diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index a9756d69..9912258f 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -79,8 +79,8 @@ jobs:
strategy:
fail-fast: true
matrix:
- python-version: ["3.10"] # ["3.8"]
- nautobot-version: ["2.0.0-rc.2"] # ["1.5.3"]
+ python-version: ["3.11"]
+ nautobot-version: ["2.0.0-rc.2"]
env:
INVOKE_NAUTOBOT_GOLDEN_CONFIG_PYTHON_VER: "${{ matrix.python-version }}"
INVOKE_NAUTOBOT_GOLDEN_CONFIG_NAUTOBOT_VER: "${{ matrix.nautobot-version }}"
@@ -116,7 +116,7 @@ jobs:
strategy:
fail-fast: true
matrix:
- python-version: ["3.10"] # ["3.7", "3.8", "3.9", "3.10"]
+ python-version: ["3.8", "3.9", "3.10", "3.11"]
db-backend: ["postgresql"]
nautobot-version: ["2.0.0-rc.2"] # ["latest"]
# The include is a method to limit the amount of jobs ran. This essentially
@@ -125,13 +125,13 @@ jobs:
include:
- python-version: "3.11"
db-backend: "postgresql"
- nautobot-version: "2.0.0-rc.2" # "1.5.3"
- - python-version: "3.10" # "3.7"
+ nautobot-version: "2.0.0-rc.2"
+ - python-version: "3.11"
+ db-backend: "mysql"
+ nautobot-version: "2.0.0-rc.2"
+ - python-version: "3.8"
db-backend: "mysql"
- nautobot-version: "2.0.0-rc.2" # "1.5.3"
- #- python-version: "3.10"
- # db-backend: "mysql"
- # nautobot-version: "latest"
+ nautobot-version: "latest"
runs-on: "ubuntu-20.04"
env:
INVOKE_NAUTOBOT_GOLDEN_CONFIG_PYTHON_VER: "${{ matrix.python-version }}"
@@ -206,7 +206,7 @@ jobs:
- name: "Set up Python"
uses: "actions/setup-python@v2"
with:
- python-version: "3.10" # "3.9"
+ python-version: "3.11"
- name: "Install Python Packages"
run: "pip install poetry"
- name: "Set env"
diff --git a/.readthedocs.yaml b/.readthedocs.yaml
index a9d358ef..a6334706 100644
--- a/.readthedocs.yaml
+++ b/.readthedocs.yaml
@@ -10,7 +10,7 @@ version: 2
build:
os: "ubuntu-22.04"
tools:
- python: "3.10"
+ python: "3.11"
mkdocs:
configuration: "mkdocs.yml"
diff --git a/development/Dockerfile b/development/Dockerfile
index 7afbab89..7b596e27 100644
--- a/development/Dockerfile
+++ b/development/Dockerfile
@@ -6,11 +6,11 @@
# -------------------------------------------------------------------------------------
# !!! USE CAUTION WHEN MODIFYING LINES BELOW
-# Accepts a desired Nautobot version as build argument, default to 1.5.3
+# Accepts a desired Nautobot version as build argument, default to 2.0.0
ARG NAUTOBOT_VER="2.0.0-rc.2"
-# Accepts a desired Python version as build argument, default to 3.7
-ARG PYTHON_VER="3.10"
+# Accepts a desired Python version as build argument, default to 3.11
+ARG PYTHON_VER="3.11"
# Retrieve published development image of Nautobot base which should include most CI dependencies
FROM ghcr.io/nautobot/nautobot-dev:${NAUTOBOT_VER}-py${PYTHON_VER}
diff --git a/development/docker-compose.dev.yml b/development/docker-compose.dev.yml
index 5987d5fb..b95ecb81 100644
--- a/development/docker-compose.dev.yml
+++ b/development/docker-compose.dev.yml
@@ -12,6 +12,13 @@ services:
volumes:
- "./nautobot_config.py:/opt/nautobot/nautobot_config.py"
- "../:/source"
+ # Helper method to mount on top of the python implementations, assuming you are using py3.11 and
+ # have all of your projects in the same directory. Uncomment out as required.
+ # - "../../netutils/netutils:/opt/nautobot/lib/python3.11/site-packages/netutils"
+ # - "../../nornir-nautobot/nornir_nautobot:/opt/nautobot/lib/python3.11/site-packages/nornir_nautobot"
+ # - "../../nautobot-plugin-nornir/nautobot_plugin_nornir:/opt/nautobot/lib/python3.11/site-packages/nautobot_plugin_nornir"
+ # - "../../nautobot/nautobot:/opt/nautobot/lib/python3.11/site-packages/nautobot"
+
docs:
entrypoint: "mkdocs serve -v -a 0.0.0.0:8080"
ports:
@@ -26,6 +33,12 @@ services:
volumes:
- "./nautobot_config.py:/opt/nautobot/nautobot_config.py"
- "../:/source"
+ # Helper method to mount on top of the python implementations, assuming you are using py3.11 and
+ # have all of your projects in the same directory. Uncomment out as required.
+ # - "../../netutils/netutils:/opt/nautobot/lib/python3.11/site-packages/netutils"
+ # - "../../nornir-nautobot/nornir_nautobot:/opt/nautobot/lib/python3.11/site-packages/nornir_nautobot"
+ # - "../../nautobot-plugin-nornir/nautobot_plugin_nornir:/opt/nautobot/lib/python3.11/site-packages/nautobot_plugin_nornir"
+ # - "../../nautobot/nautobot:/opt/nautobot/lib/python3.11/site-packages/nautobot"
# To expose postgres or redis to the host uncomment the following
# postgres:
# ports:
diff --git a/docs/admin/release_notes/version_1.4.md b/docs/admin/release_notes/version_1.4.md
index 06251351..d03fc888 100755
--- a/docs/admin/release_notes/version_1.4.md
+++ b/docs/admin/release_notes/version_1.4.md
@@ -6,55 +6,55 @@
### Changed
-- [519](https://github.com/nautobot/nautobot-plugin-golden-config/pull/519) - docs-only: large fixes and template troubleshooting section.
+- [#519](https://github.com/nautobot/nautobot-plugin-golden-config/pull/519) - docs-only: large fixes and template troubleshooting section.
### Fixed
-- [492](https://github.com/nautobot/nautobot-plugin-golden-config/pull/492) - Fix count of in scope devices on settings detail view.
-- [498](https://github.com/nautobot/nautobot-plugin-golden-config/pull/498) - Fix deepdiff dependency.
-- [501](https://github.com/nautobot/nautobot-plugin-golden-config/pull/501) - Update docs for adding CustomField data with datasources.
-- [503](https://github.com/nautobot/nautobot-plugin-golden-config/pull/503) - Switch from deprecated FilterSet to new FilterSetMixin.
-- [504](https://github.com/nautobot/nautobot-plugin-golden-config/pull/504) - Fix extend queryfilter to export.
-- [511](https://github.com/nautobot/nautobot-plugin-golden-config/pull/511) - Fix `log_failure` function missing argument.
-- [523](https://github.com/nautobot/nautobot-plugin-golden-config/pull/523) - Fix docs site by pinning dev dependencies.
-- [530](https://github.com/nautobot/nautobot-plugin-golden-config/pull/530) - Fix, removing ConfigCompliance model import from 0005 migration.
+- [#492](https://github.com/nautobot/nautobot-plugin-golden-config/pull/492) - Fix count of in scope devices on settings detail view.
+- [#498](https://github.com/nautobot/nautobot-plugin-golden-config/pull/498) - Fix deepdiff dependency.
+- [#501](https://github.com/nautobot/nautobot-plugin-golden-config/pull/501) - Update docs for adding CustomField data with datasources.
+- [#503](https://github.com/nautobot/nautobot-plugin-golden-config/pull/503) - Switch from deprecated FilterSet to new FilterSetMixin.
+- [#504](https://github.com/nautobot/nautobot-plugin-golden-config/pull/504) - Fix extend queryfilter to export.
+- [#511](https://github.com/nautobot/nautobot-plugin-golden-config/pull/511) - Fix `log_failure` function missing argument.
+- [#523](https://github.com/nautobot/nautobot-plugin-golden-config/pull/523) - Fix docs site by pinning dev dependencies.
+- [#530](https://github.com/nautobot/nautobot-plugin-golden-config/pull/530) - Fix, removing ConfigCompliance model import from 0005 migration.
## v1.4.1 - 2023-05
### Fixed
-- [488](https://github.com/nautobot/nautobot-plugin-golden-config/pull/488) - Fix Golden Config Settings Buttons.
+- [#488](https://github.com/nautobot/nautobot-plugin-golden-config/pull/488) - Fix Golden Config Settings Buttons.
## v1.4.0 - 2023-05
### Added
-- [445](https://github.com/nautobot/nautobot-plugin-golden-config/pull/445) - Add validation for Settings sot_agg_query.
-- [449](https://github.com/nautobot/nautobot-plugin-golden-config/pull/449) - Allows for custom kwargs to `get_secret_by_secret_group_slug`.
-- [470](https://github.com/nautobot/nautobot-plugin-golden-config/pull/470) - Enhance UI settings detail object view.
-- [473](https://github.com/nautobot/nautobot-plugin-golden-config/pull/473) - Add status selection field to job filtering.
-- [480](https://github.com/nautobot/nautobot-plugin-golden-config/pull/480) - Add compliance summary to default tenant view.
+- [#445](https://github.com/nautobot/nautobot-plugin-golden-config/pull/445) - Add validation for Settings sot_agg_query.
+- [#449](https://github.com/nautobot/nautobot-plugin-golden-config/pull/449) - Allows for custom kwargs to `get_secret_by_secret_group_slug`.
+- [#470](https://github.com/nautobot/nautobot-plugin-golden-config/pull/470) - Enhance UI settings detail object view.
+- [#473](https://github.com/nautobot/nautobot-plugin-golden-config/pull/473) - Add status selection field to job filtering.
+- [#480](https://github.com/nautobot/nautobot-plugin-golden-config/pull/480) - Add compliance summary to default tenant view.
### Changed
-- [414](https://github.com/nautobot/nautobot-plugin-golden-config/pull/414) - Update application description for UI.
-- [407](https://github.com/nautobot/nautobot-plugin-golden-config/pull/407) - Update branching policy in contributing docs.
-- [417](https://github.com/nautobot/nautobot-plugin-golden-config/pull/417) - Changed extends base.html to extends generic/object_detail.html.
-- [434](https://github.com/nautobot/nautobot-plugin-golden-config/pull/434) - Upgrade deepdiff dependency to 6.2.0.
-- [451](https://github.com/nautobot/nautobot-plugin-golden-config/pull/451) - Tune Dependabot.
-- [459](https://github.com/nautobot/nautobot-plugin-golden-config/pull/459) - Update tasks.py to meet current standards.
-- [464](https://github.com/nautobot/nautobot-plugin-golden-config/pull/464) - Update ordering on compliance views.
-- [471](https://github.com/nautobot/nautobot-plugin-golden-config/pull/471) - Migrate to using NautobotUIViewset and other initial 2.x prep work.
-- [481](https://github.com/nautobot/nautobot-plugin-golden-config/pull/481) - Update filtersets for rack-group to extend proper TreeNode parent.
+- [#414](https://github.com/nautobot/nautobot-plugin-golden-config/pull/414) - Update application description for UI.
+- [#407](https://github.com/nautobot/nautobot-plugin-golden-config/pull/407) - Update branching policy in contributing docs.
+- [#417](https://github.com/nautobot/nautobot-plugin-golden-config/pull/417) - Changed extends base.html to extends generic/object_detail.html.
+- [#434](https://github.com/nautobot/nautobot-plugin-golden-config/pull/434) - Upgrade deepdiff dependency to 6.2.0.
+- [#451](https://github.com/nautobot/nautobot-plugin-golden-config/pull/451) - Tune Dependabot.
+- [#459](https://github.com/nautobot/nautobot-plugin-golden-config/pull/459) - Update tasks.py to meet current standards.
+- [#464](https://github.com/nautobot/nautobot-plugin-golden-config/pull/464) - Update ordering on compliance views.
+- [#471](https://github.com/nautobot/nautobot-plugin-golden-config/pull/471) - Migrate to using NautobotUIViewset and other initial 2.x prep work.
+- [#481](https://github.com/nautobot/nautobot-plugin-golden-config/pull/481) - Update filtersets for rack-group to extend proper TreeNode parent.
### Fixed
-- [436](https://github.com/nautobot/nautobot-plugin-golden-config/pull/436) - Update FAQ for how compliance works.
-- [444](https://github.com/nautobot/nautobot-plugin-golden-config/pull/444) - `app_faq.md` references incorrect `Cisco IOS XR` platform slug.
-- [446](https://github.com/nautobot/nautobot-plugin-golden-config/pull/446) - Fix mysql not working in github actions.
-- [450](https://github.com/nautobot/nautobot-plugin-golden-config/pull/450) - Make ConfigReplace export match import.
-- [456](https://github.com/nautobot/nautobot-plugin-golden-config/pull/456) - Fix postprocessing to use Sandbox Jinja2 environment.
-- [461](https://github.com/nautobot/nautobot-plugin-golden-config/pull/461) - Moves dependabot config to proper location.
-- [463](https://github.com/nautobot/nautobot-plugin-golden-config/pull/463) - Fix Json render in compliance reporting template.
-- [468](https://github.com/nautobot/nautobot-plugin-golden-config/pull/468) - Fix GoldenConfig list view and csv export.
-- [474](https://github.com/nautobot/nautobot-plugin-golden-config/pull/474) - Docs update: Fix multiple typos.
+- [#436](https://github.com/nautobot/nautobot-plugin-golden-config/pull/436) - Update FAQ for how compliance works.
+- [#444](https://github.com/nautobot/nautobot-plugin-golden-config/pull/444) - `app_faq.md` references incorrect `Cisco IOS XR` platform slug.
+- [#446](https://github.com/nautobot/nautobot-plugin-golden-config/pull/446) - Fix mysql not working in github actions.
+- [#450](https://github.com/nautobot/nautobot-plugin-golden-config/pull/450) - Make ConfigReplace export match import.
+- [#456](https://github.com/nautobot/nautobot-plugin-golden-config/pull/456) - Fix postprocessing to use Sandbox Jinja2 environment.
+- [#461](https://github.com/nautobot/nautobot-plugin-golden-config/pull/461) - Moves dependabot config to proper location.
+- [#463](https://github.com/nautobot/nautobot-plugin-golden-config/pull/463) - Fix Json render in compliance reporting template.
+- [#468](https://github.com/nautobot/nautobot-plugin-golden-config/pull/468) - Fix GoldenConfig list view and csv export.
+- [#474](https://github.com/nautobot/nautobot-plugin-golden-config/pull/474) - Docs update: Fix multiple typos.
diff --git a/docs/admin/release_notes/version_1.5.md b/docs/admin/release_notes/version_1.5.md
index 7d4be037..46e0d40f 100755
--- a/docs/admin/release_notes/version_1.5.md
+++ b/docs/admin/release_notes/version_1.5.md
@@ -11,17 +11,17 @@
### Added
-- [455](https://github.com/nautobot/nautobot-plugin-golden-config/pull/455) - Add metrics for Golden Config plugin.
-- [485](https://github.com/nautobot/nautobot-plugin-golden-config/pull/485) - Custom compliance for CLI and JSON rules.
-- [487](https://github.com/nautobot/nautobot-plugin-golden-config/pull/487) - Implement native JSON support.
-- [527](https://github.com/nautobot/nautobot-plugin-golden-config/pull/527) - Add the ability to update Jinja environment setting from nautobot_config.
-- [558](https://github.com/nautobot/nautobot-plugin-golden-config/pull/558) - Updated Filters for various models, including adding an experimental `_isnull` on DateTime objects.
+- [#455](https://github.com/nautobot/nautobot-plugin-golden-config/pull/455) - Add metrics for Golden Config plugin.
+- [#485](https://github.com/nautobot/nautobot-plugin-golden-config/pull/485) - Custom compliance for CLI and JSON rules.
+- [#487](https://github.com/nautobot/nautobot-plugin-golden-config/pull/487) - Implement native JSON support.
+- [#527](https://github.com/nautobot/nautobot-plugin-golden-config/pull/527) - Add the ability to update Jinja environment setting from nautobot_config.
+- [#558](https://github.com/nautobot/nautobot-plugin-golden-config/pull/558) - Updated Filters for various models, including adding an experimental `_isnull` on DateTime objects.
### Changed
-- [485](https://github.com/nautobot/nautobot-plugin-golden-config/pull/485) - Changed the behavior of custom compliance to a boolean vs toggle between cli, json, and custom.
+- [#485](https://github.com/nautobot/nautobot-plugin-golden-config/pull/485) - Changed the behavior of custom compliance to a boolean vs toggle between cli, json, and custom.
### Fixed
-- [505](https://github.com/nautobot/nautobot-plugin-golden-config/pull/505) - fixes imports and choice definitions in the compliance nornir play.
-- [513](https://github.com/nautobot/nautobot-plugin-golden-config/pull/513) - Fixed issue with native JSON support with `get_config_element` function.
+- [#505](https://github.com/nautobot/nautobot-plugin-golden-config/pull/505) - fixes imports and choice definitions in the compliance nornir play.
+- [#513](https://github.com/nautobot/nautobot-plugin-golden-config/pull/513) - Fixed issue with native JSON support with `get_config_element` function.
diff --git a/docs/dev/dev_environment.md b/docs/dev/dev_environment.md
index e2c8a06a..1d55d564 100644
--- a/docs/dev/dev_environment.md
+++ b/docs/dev/dev_environment.md
@@ -15,7 +15,7 @@ The [Invoke](http://www.pyinvoke.org/) library is used to provide some helper co
- `nautobot_ver`: the version of Nautobot to use as a base for any built docker containers (default: latest)
- `project_name`: the default docker compose project name (default: `nautobot_golden_config`)
-- `python_ver`: the version of Python to use as a base for any built docker containers (default: 3.8)
+- `python_ver`: the version of Python to use as a base for any built docker containers (default: 3.11)
- `local`: a boolean flag indicating if invoke tasks should be run on the host or inside the docker containers (default: False, commands will be run in docker containers)
- `compose_dir`: the full path to a directory containing the project compose files
- `compose_files`: a list of compose files applied in order (see [Multiple Compose files](https://docs.docker.com/compose/extends/#multiple-compose-files) for more information)
@@ -187,7 +187,7 @@ The first thing you need to do is build the necessary Docker image for Nautobot
#14 exporting layers
#14 exporting layers 1.2s done
#14 writing image sha256:2d524bc1665327faa0d34001b0a9d2ccf450612bf8feeb969312e96a2d3e3503 done
-#14 naming to docker.io/nautobot-golden-config/nautobot:latest-py3.7 done
+#14 naming to docker.io/nautobot-golden-config/nautobot:latest-py3.11 done
```
### Invoke - Starting the Development Environment
@@ -218,9 +218,9 @@ This will start all of the Docker containers used for hosting Nautobot. You shou
```bash
➜ docker ps
****CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
-ee90fbfabd77 nautobot-golden-config/nautobot:latest-py3.7 "nautobot-server rqw…" 16 seconds ago Up 13 seconds nautobot_golden_config_worker_1
-b8adb781d013 nautobot-golden-config/nautobot:latest-py3.7 "/docker-entrypoint.…" 20 seconds ago Up 15 seconds 0.0.0.0:8080->8080/tcp, :::8080->8080/tcp nautobot_golden_config_nautobot_1
-d64ebd60675d nautobot-golden-config/nautobot:latest-py3.7 "mkdocs serve -v -a …" 25 seconds ago Up 18 seconds 0.0.0.0:8001->8080/tcp, :::8001->8080/tcp nautobot_golden_config_docs_1
+ee90fbfabd77 nautobot-golden-config/nautobot:latest-py3.11 "nautobot-server rqw…" 16 seconds ago Up 13 seconds nautobot_golden_config_worker_1
+b8adb781d013 nautobot-golden-config/nautobot:latest-py3.11 "/docker-entrypoint.…" 20 seconds ago Up 15 seconds 0.0.0.0:8080->8080/tcp, :::8080->8080/tcp nautobot_golden_config_nautobot_1
+d64ebd60675d nautobot-golden-config/nautobot:latest-py3.11 "mkdocs serve -v -a …" 25 seconds ago Up 18 seconds 0.0.0.0:8001->8080/tcp, :::8001->8080/tcp nautobot_golden_config_docs_1
e72d63129b36 postgres:13-alpine "docker-entrypoint.s…" 25 seconds ago Up 19 seconds 0.0.0.0:5432->5432/tcp, :::5432->5432/tcp nautobot_golden_config_postgres_1
96c6ff66997c redis:6-alpine "docker-entrypoint.s…" 25 seconds ago Up 21 seconds 0.0.0.0:6379->6379/tcp, :::6379->6379/tcp nautobot_golden_config_redis_1
```
@@ -399,7 +399,7 @@ namespace.configure(
{
"nautobot_golden_config": {
...
- "python_ver": "3.7",
+ "python_ver": "3.11",
...
}
}
diff --git a/docs/user/troubleshooting/troubleshoot_dispatchers.md b/docs/user/troubleshooting/troubleshoot_dispatchers.md
index 2bc03fda..ed044d5b 100755
--- a/docs/user/troubleshooting/troubleshoot_dispatchers.md
+++ b/docs/user/troubleshooting/troubleshoot_dispatchers.md
@@ -14,6 +14,8 @@ This occurs when a Golden Config job is executed with a Nautobot `platform`, and
How is the dispatcher loaded?
+TODO: 2.0: Change to custom_dispatcher
+
1. Job initializes Nornir and the method is called with `get_dispatcher()` function from Nautobot-Plugin-Nornir.
2. Nornir initialization looks in the DEFAULT_DISPATCHER map for the platform network_driver from [nornir-nautobot](https://github.com/nautobot/nornir-nautobot/blob/64baa8a24d21d9ec14c32be569e2b51cd0bd1cd1/nornir_nautobot/plugins/tasks/dispatcher/__init__.py#L12) mapping.
3. Merge this mapping with anything directly configured in Golden Config [dispatcher mapping]().
diff --git a/nautobot_golden_config/api/serializers.py b/nautobot_golden_config/api/serializers.py
index 5de9b7b2..a21fcc3d 100644
--- a/nautobot_golden_config/api/serializers.py
+++ b/nautobot_golden_config/api/serializers.py
@@ -2,16 +2,10 @@
# pylint: disable=too-many-ancestors
from rest_framework import serializers
-from nautobot.apps.api import WritableNestedSerializer
-from nautobot.extras.api.fields import StatusSerializerField
-from nautobot.extras.api.serializers import TaggedObjectSerializer
-from nautobot.extras.api.nested_serializers import NestedDynamicGroupSerializer
from nautobot.extras.api.mixins import TaggedModelSerializerMixin
-from nautobot.extras.models import Status
-from nautobot.dcim.api.nested_serializers import NestedDeviceSerializer
from nautobot.dcim.api.serializers import DeviceSerializer
from nautobot.dcim.models import Device
-from nautobot.extras.api.serializers import NautobotModelSerializer, StatusModelSerializerMixin
+from nautobot.core.api.serializers import NautobotModelSerializer
from nautobot_golden_config import models
@@ -78,7 +72,7 @@ class GoldenConfigSettingSerializer(NautobotModelSerializer, TaggedModelSerializ
url = serializers.HyperlinkedIdentityField(
view_name="plugins-api:nautobot_golden_config-api:goldenconfigsetting-detail"
)
- # TODO: What is correct for this with the removal of nested serializers?
+ # TODO: 2.0: What is correct for this with the removal of nested serializers?
# dynamic_group = NestedDynamicGroupSerializer(required=False)
class Meta:
@@ -120,7 +114,7 @@ class ConfigToPushSerializer(DeviceSerializer):
class Meta(DeviceSerializer):
"""Extend the Device serializer with the configuration after postprocessing."""
- # TODO: Fix fields to work with Device moving to a string "__all__"
+ # TODO: 2.0: Fix fields to work with Device moving to a string "__all__"
# fields = DeviceSerializer.Meta.fields + ["config"]
fields = "__all__"
model = Device
@@ -133,7 +127,7 @@ def get_config(self, obj):
return get_config_postprocessing(config_details, request)
-class RemediationSettingSerializer(NautobotModelSerializer, TaggedObjectSerializer):
+class RemediationSettingSerializer(NautobotModelSerializer, TaggedModelSerializerMixin):
"""Serializer for RemediationSetting object."""
url = serializers.HyperlinkedIdentityField(
@@ -148,12 +142,10 @@ class Meta:
fields = "__all__"
-class ConfigPlanSerializer(NautobotModelSerializer, TaggedObjectSerializer, StatusModelSerializerMixin):
+class ConfigPlanSerializer(NautobotModelSerializer, TaggedModelSerializerMixin):
"""Serializer for ConfigPlan object."""
url = serializers.HyperlinkedIdentityField(view_name="plugins-api:nautobot_golden_config-api:configplan-detail")
- device = NestedDeviceSerializer(required=False)
- status = StatusSerializerField(required=False, queryset=Status.objects.all())
class Meta:
"""Set Meta Data for ConfigPlan, will serialize all fields."""
@@ -161,15 +153,3 @@ class Meta:
model = models.ConfigPlan
fields = "__all__"
read_only_fields = ["device", "plan_type", "feature", "config_set"]
-
-
-class NestedConfigPlanSerializer(WritableNestedSerializer):
- """Nested serializer for ConfigPlan object."""
-
- url = serializers.HyperlinkedIdentityField(view_name="plugins-api:nautobot_golden_config-api:configplan-detail")
-
- class Meta:
- """Set Meta Data for ConfigPlan, will serialize brief fields."""
-
- model = models.ConfigPlan
- fields = ["id", "url", "device", "plan_type"]
diff --git a/nautobot_golden_config/filters.py b/nautobot_golden_config/filters.py
index 95af7527..1d69c3d1 100644
--- a/nautobot_golden_config/filters.py
+++ b/nautobot_golden_config/filters.py
@@ -3,24 +3,17 @@
import django_filters
from django.db.models import Q
-from nautobot.core.filters import (
- BaseFilterSet,
- MultiValueDateTimeFilter,
- NameSlugSearchFilterSet,
- TagFilter,
- TreeNodeMultipleChoiceFilter,
-)
+from nautobot.core.filters import BaseFilterSet, MultiValueDateTimeFilter, TagFilter, TreeNodeMultipleChoiceFilter
from nautobot.dcim.models import Device, DeviceType, Manufacturer, Platform, Rack, RackGroup, Location
from nautobot.dcim.filters import DeviceFilterSet
-from nautobot.extras.filters import NaturalKeyOrPKMultipleChoiceFilter, StatusFilter
-from nautobot.extras.filters import NautobotFilterSet
-from nautobot.extras.models import Status, Role
+from nautobot.extras.filters import NaturalKeyOrPKMultipleChoiceFilter, NautobotFilterSet, StatusFilter
+from nautobot.extras.models import JobResult, Role, Status
from nautobot.tenancy.models import Tenant, TenantGroup
from nautobot_golden_config import models
-# TODO: DeviceFilterSet has bugs in regards to Location in 2.0.0-rc.2
+# TODO: 2.0: DeviceFilterSet has bugs in regards to Location in 2.0.0-rc.2
class GoldenConfigDeviceFilterSet(DeviceFilterSet): # pylint: disable=too-many-ancestors
"""Filter capabilities that extend the standard DeviceFilterSet."""
@@ -119,7 +112,7 @@ def _get_filter_lookup_dict(existing_filter):
)
role = NaturalKeyOrPKMultipleChoiceFilter(
field_name="device__role",
- queryset=Role.objects.all(), # TODO: How does change to Role model affect this?
+ queryset=Role.objects.all(), # TODO: 2.0: How does change to Role model affect this?
to_field_name="name",
label="Role (name or ID)",
)
@@ -318,7 +311,7 @@ class Meta:
fields = ["id", "name", "slug", "weight", "backup_repository", "intended_repository", "jinja_repository"]
-class RemediationSettingFilterSet(BaseFilterSet, NameSlugSearchFilterSet):
+class RemediationSettingFilterSet(BaseFilterSet):
"""Inherits Base Class CustomFieldModelFilterSet."""
q = django_filters.CharFilter(
@@ -356,7 +349,7 @@ class Meta:
fields = ["id", "platform", "remediation_type"]
-class ConfigPlanFilterSet(BaseFilterSet, NameSlugSearchFilterSet):
+class ConfigPlanFilterSet(BaseFilterSet):
"""Inherits Base Class BaseFilterSet."""
q = django_filters.CharFilter(
diff --git a/nautobot_golden_config/forms.py b/nautobot_golden_config/forms.py
index ae71a4d7..9d2db033 100644
--- a/nautobot_golden_config/forms.py
+++ b/nautobot_golden_config/forms.py
@@ -8,9 +8,10 @@
import nautobot.core.forms as core_forms
from nautobot.dcim.models import Device, Platform, Location, DeviceType, Manufacturer, Rack, RackGroup
from nautobot.extras.forms import NautobotFilterForm, NautobotBulkEditForm, NautobotModelForm
-from nautobot.extras.models import Status, GitRepository, DynamicGroup, Role
+from nautobot.extras.models import DynamicGroup, GitRepository, JobResult, Role, Status, Tag
from nautobot.tenancy.models import Tenant, TenantGroup
-import nautobot.utilities.forms as utilities_forms
+
+# import nautobot.utilities.forms as core_forms
from nautobot_golden_config import models
from nautobot_golden_config.choices import ComplianceRuleConfigTypeChoice, ConfigPlanTypeChoice, RemediationTypeChoice
@@ -96,7 +97,7 @@ class ConfigComplianceFilterForm(NautobotFilterForm):
role = core_forms.DynamicModelMultipleChoiceField(
queryset=Role.objects.all(),
to_field_name="name",
- required=False, # TODO: Test with change to Role model
+ required=False, # TODO: 2.0: Test with change to Role model
)
manufacturer = core_forms.DynamicModelMultipleChoiceField(
queryset=Manufacturer.objects.all(), to_field_name="name", required=False, label="Manufacturer"
@@ -176,11 +177,11 @@ class ComplianceRuleBulkEditForm(NautobotBulkEditForm):
description = forms.CharField(max_length=200, required=False)
config_type = forms.ChoiceField(
required=False,
- choices=utilities_forms.add_blank_choice(ComplianceRuleConfigTypeChoice),
+ choices=core_forms.add_blank_choice(ComplianceRuleConfigTypeChoice),
)
- config_ordered = forms.NullBooleanField(required=False, widget=utilities_forms.BulkEditNullBooleanSelect())
- custom_compliance = forms.NullBooleanField(required=False, widget=utilities_forms.BulkEditNullBooleanSelect())
- config_remediation = forms.NullBooleanField(required=False, widget=utilities_forms.BulkEditNullBooleanSelect())
+ config_ordered = forms.NullBooleanField(required=False, widget=core_forms.BulkEditNullBooleanSelect())
+ custom_compliance = forms.NullBooleanField(required=False, widget=core_forms.BulkEditNullBooleanSelect())
+ config_remediation = forms.NullBooleanField(required=False, widget=core_forms.BulkEditNullBooleanSelect())
class Meta:
"""Boilerplate form Meta data for ComplianceRule."""
@@ -194,7 +195,7 @@ class Meta:
class ComplianceFeatureForm(NautobotModelForm):
"""Filter Form for ComplianceFeature instances."""
- slug = core_forms.fields.SlugField() # TODO: Remove slugs
+ slug = core_forms.fields.SlugField() # TODO: 2.1: Change from slugs once django-pivot is figured out
class Meta:
"""Boilerplate form Meta data for compliance feature."""
@@ -321,8 +322,8 @@ class Meta:
class GoldenConfigSettingForm(NautobotModelForm):
"""Filter Form for GoldenConfigSettingForm instances."""
- slug = core_forms.fields.SlugField() # TODO: Remove slugs
- dynamic_group = core_forms.DynamicModelChoiceField(queryset=DynamicGroup.objects.all(), required=False)
+ slug = core_forms.fields.SlugField() # TODO: 2.1: Remove slugs
+ dynamic_group = core_forms.DynamicModelChoiceField(queryset=DynamicGroup.objects.all())
class Meta:
"""Filter Form Meta Data for GoldenConfigSettingForm instances."""
@@ -401,24 +402,14 @@ class RemediationSettingFilterForm(NautobotFilterForm):
model = models.RemediationSetting
q = forms.CharField(required=False, label="Search")
- platform = utilities_forms.DynamicModelMultipleChoiceField(
+ platform = core_forms.DynamicModelMultipleChoiceField(
queryset=Platform.objects.all(), required=False, display_field="name", to_field_name="name"
)
remediation_type = forms.ChoiceField(
- choices=add_blank_choice(RemediationTypeChoice), required=False, label="Remediation Type"
+ choices=core_forms.add_blank_choice(RemediationTypeChoice), required=False, label="Remediation Type"
)
-class RemediationSettingCSVForm(extras_forms.CustomFieldModelCSVForm):
- """CSV Form for RemediationSetting instances."""
-
- class Meta:
- """Boilerplate form Meta data for RemediationSetting."""
-
- model = models.RemediationSetting
- fields = models.RemediationSetting.csv_headers
-
-
class RemediationSettingBulkEditForm(NautobotBulkEditForm):
"""BulkEdit form for RemediationSetting instances."""
@@ -439,14 +430,15 @@ class Meta:
class ConfigPlanForm(NautobotModelForm):
"""Form for ConfigPlan instances."""
- plan_type = forms.ChoiceField(choices=add_blank_choice(ConfigPlanTypeChoice), required=True, label="Plan Type")
- change_control_id = forms.CharField(required=False, label="Change Control ID")
- change_control_url = forms.URLField(required=False, label="Change Control URL")
+ plan_type = forms.ChoiceField(
+ choices=core_forms.add_blank_choice(ConfigPlanTypeChoice), required=True, label="Plan Type"
+ )
+ change_control_id = forms.CharField(label="Change Control ID")
+ change_control_url = forms.URLField(label="Change Control URL")
- feature = utilities_forms.DynamicModelMultipleChoiceField(
+ feature = core_forms.DynamicModelMultipleChoiceField(
queryset=models.ComplianceFeature.objects.all(),
display_field="name",
- required=False,
help_text="Note: Selecting no features will generate plans for all applicable features.",
)
commands = forms.CharField(
@@ -457,27 +449,26 @@ class ConfigPlanForm(NautobotModelForm):
"You can also reference the device object with obj
.
"
"For example: hostname {{ obj.name }}
or ip address {{ obj.primary_ip4.host }}
"
),
- required=True,
)
- tenant_group = utilities_forms.DynamicModelMultipleChoiceField(queryset=TenantGroup.objects.all(), required=False)
- tenant = utilities_forms.DynamicModelMultipleChoiceField(queryset=Tenant.objects.all(), required=False)
+ tenant_group = core_forms.DynamicModelMultipleChoiceField(queryset=TenantGroup.objects.all())
+ tenant = core_forms.DynamicModelMultipleChoiceField(queryset=Tenant.objects.all())
# Requires https://github.com/nautobot/nautobot-plugin-golden-config/issues/430
- # location = utilities_forms.DynamicModelMultipleChoiceField(queryset=Location.objects.all(), required=False)
- region = utilities_forms.DynamicModelMultipleChoiceField(queryset=Region.objects.all(), required=False)
- site = utilities_forms.DynamicModelMultipleChoiceField(queryset=Site.objects.all(), required=False)
- rack_group = utilities_forms.DynamicModelMultipleChoiceField(queryset=RackGroup.objects.all(), required=False)
- rack = utilities_forms.DynamicModelMultipleChoiceField(queryset=Rack.objects.all(), required=False)
- role = utilities_forms.DynamicModelMultipleChoiceField(queryset=DeviceRole.objects.all(), required=False)
- manufacturer = utilities_forms.DynamicModelMultipleChoiceField(queryset=Manufacturer.objects.all(), required=False)
- platform = utilities_forms.DynamicModelMultipleChoiceField(queryset=Platform.objects.all(), required=False)
- device_type = utilities_forms.DynamicModelMultipleChoiceField(queryset=DeviceType.objects.all(), required=False)
- device = utilities_forms.DynamicModelMultipleChoiceField(queryset=Device.objects.all(), required=False)
- tag = utilities_forms.DynamicModelMultipleChoiceField(
- queryset=Tag.objects.all(), query_params={"content_types": "dcim.device"}, required=False
- )
- status = utilities_forms.DynamicModelMultipleChoiceField(
- queryset=Status.objects.all(), query_params={"content_types": "dcim.device"}, required=False
+ location = core_forms.DynamicModelMultipleChoiceField(queryset=Location.objects.all())
+ # region = core_forms.DynamicModelMultipleChoiceField(queryset=Region.objects.all())
+ # site = core_forms.DynamicModelMultipleChoiceField(queryset=Site.objects.all())
+ rack_group = core_forms.DynamicModelMultipleChoiceField(queryset=RackGroup.objects.all())
+ rack = core_forms.DynamicModelMultipleChoiceField(queryset=Rack.objects.all())
+ role = core_forms.DynamicModelMultipleChoiceField(queryset=Role.objects.all())
+ manufacturer = core_forms.DynamicModelMultipleChoiceField(queryset=Manufacturer.objects.all())
+ platform = core_forms.DynamicModelMultipleChoiceField(queryset=Platform.objects.all())
+ device_type = core_forms.DynamicModelMultipleChoiceField(queryset=DeviceType.objects.all())
+ device = core_forms.DynamicModelMultipleChoiceField(queryset=Device.objects.all())
+ tag = core_forms.DynamicModelMultipleChoiceField(
+ queryset=Tag.objects.all(), query_params={"content_types": "dcim.device"}
+ )
+ status = core_forms.DynamicModelMultipleChoiceField(
+ queryset=Status.objects.all(), query_params={"content_types": "dcim.device"}
)
def __init__(self, *args, **kwargs):
@@ -508,9 +499,7 @@ class Meta:
"feature",
"commands",
"tenant",
- # "location", Requires https://github.com/nautobot/nautobot-plugin-golden-config/issues/430
- "region",
- "site",
+ "location", # Requires https://github.com/nautobot/nautobot-plugin-golden-config/issues/430
"rack_group",
"rack",
"role",
@@ -526,15 +515,14 @@ class Meta:
class ConfigPlanUpdateForm(NautobotModelForm):
"""Form for ConfigPlan instances."""
- change_control_id = forms.CharField(required=False, label="Change Control ID")
- change_control_url = forms.URLField(required=False, label="Change Control URL")
- status = utilities_forms.DynamicModelChoiceField(
+ change_control_id = forms.CharField(label="Change Control ID")
+ change_control_url = forms.URLField(label="Change Control URL")
+ status = core_forms.DynamicModelChoiceField(
queryset=Status.objects.all(),
query_params={"content_types": models.ConfigPlan._meta.label_lower},
- required=False,
)
- tag = utilities_forms.DynamicModelMultipleChoiceField(
- queryset=Tag.objects.all(), query_params={"content_types": "dcim.device"}, required=False
+ tag = core_forms.DynamicModelMultipleChoiceField(
+ queryset=Tag.objects.all(), query_params={"content_types": "dcim.device"}
)
class Meta:
@@ -555,15 +543,18 @@ class ConfigPlanFilterForm(NautobotFilterForm):
model = models.ConfigPlan
q = forms.CharField(required=False, label="Search")
- device_id = utilities_forms.DynamicModelMultipleChoiceField(
+ device_id = core_forms.DynamicModelMultipleChoiceField(
queryset=Device.objects.all(), required=False, null_option="None", label="Device"
)
- created__lte = forms.DateTimeField(label="Created Before", required=False, widget=utilities_forms.DatePicker())
- created__gte = forms.DateTimeField(label="Created After", required=False, widget=utilities_forms.DatePicker())
+ created__lte = forms.DateTimeField(label="Created Before", required=False, widget=core_forms.DatePicker())
+ created__gte = forms.DateTimeField(label="Created After", required=False, widget=core_forms.DatePicker())
plan_type = forms.ChoiceField(
- choices=utilities_forms.add_blank_choice(ConfigPlanTypeChoice), required=False, widget=forms.Select(), label="Plan Type"
+ choices=core_forms.add_blank_choice(ConfigPlanTypeChoice),
+ required=False,
+ widget=forms.Select(),
+ label="Plan Type",
)
- feature = utilities_forms.DynamicModelMultipleChoiceField(
+ feature = core_forms.DynamicModelMultipleChoiceField(
queryset=models.ComplianceFeature.objects.all(),
required=False,
null_option="None",
@@ -571,14 +562,14 @@ class ConfigPlanFilterForm(NautobotFilterForm):
to_field_name="name",
)
change_control_id = forms.CharField(required=False, label="Change Control ID")
- job_result_id = utilities_forms.DynamicModelMultipleChoiceField(
+ job_result_id = core_forms.DynamicModelMultipleChoiceField(
queryset=JobResult.objects.all(),
query_params={"nautobot_golden_config_config_plan_null": True},
label="Job Result",
required=False,
display_field="id",
)
- status = utilities_forms.DynamicModelMultipleChoiceField(
+ status = core_forms.DynamicModelMultipleChoiceField(
required=False,
queryset=Status.objects.all(),
query_params={"content_types": models.ConfigPlan._meta.label_lower},
@@ -586,14 +577,14 @@ class ConfigPlanFilterForm(NautobotFilterForm):
label="Status",
to_field_name="name",
)
- tag = utilities_forms.TagFilterField(model)
+ tag = core_forms.TagFilterField(model)
-class ConfigPlanBulkEditForm(core_forms.TagsBulkEditFormMixin, NautobotBulkEditForm):
+class ConfigPlanBulkEditForm(NautobotBulkEditForm):
"""BulkEdit form for ConfigPlan instances."""
pk = forms.ModelMultipleChoiceField(queryset=models.ConfigPlan.objects.all(), widget=forms.MultipleHiddenInput)
- status = utilities_forms.DynamicModelChoiceField(
+ status = core_forms.DynamicModelChoiceField(
queryset=Status.objects.all(),
query_params={"content_types": models.ConfigPlan._meta.label_lower},
required=False,
diff --git a/nautobot_golden_config/jobs.py b/nautobot_golden_config/jobs.py
index 6d2578c2..09afa3c3 100644
--- a/nautobot_golden_config/jobs.py
+++ b/nautobot_golden_config/jobs.py
@@ -1,5 +1,5 @@
"""Jobs to run backups, intended config, and compliance."""
-# pylint: disable=too-many-function-args
+# pylint: disable=too-many-function-args,logging-fstring-interpolation
from datetime import datetime
@@ -63,20 +63,7 @@ def get_refreshed_repos(job_obj, repo_type, data=None):
return repositories
-def commit_check(method):
- """Decorator to check if a "dry-run" attempt was made."""
-
- def inner(obj, data, commit):
- """Decorator bolierplate code."""
- msg = "Dry-run mode is not supported, please set the commit flag to proceed."
- if not commit:
- raise ValueError(msg)
- return method(obj, data, commit)
-
- return inner
-
-
-# TODO: Does changing region/site to location affect nornir jobs?
+# TODO: 2.0: Does changing region/site to location affect nornir jobs?
class FormEntry: # pylint disable=too-few-public-method
@@ -87,7 +74,7 @@ class FormEntry: # pylint disable=too-few-public-method
location = MultiObjectVar(model=Location, required=False)
rack_group = MultiObjectVar(model=RackGroup, required=False)
rack = MultiObjectVar(model=Rack, required=False)
- role = MultiObjectVar(model=Role, required=False) # TODO: How does change to Role model affect this?
+ role = MultiObjectVar(model=Role, required=False) # TODO: 2.0: How does change to Role model affect this?
manufacturer = MultiObjectVar(model=Manufacturer, required=False)
platform = MultiObjectVar(model=Platform, required=False)
device_type = MultiObjectVar(model=DeviceType, required=False, display_field="display_name")
@@ -106,8 +93,6 @@ class FormEntry: # pylint disable=too-few-public-method
class ComplianceJob(Job, FormEntry):
"""Job to to run the compliance engine."""
- # TODO: Remove these as they are already defined via inheritence
-
tenant_group = FormEntry.tenant_group
tenant = FormEntry.tenant
location = FormEntry.location
@@ -128,23 +113,17 @@ class Meta:
name = "Perform Configuration Compliance"
description = "Run configuration compliance on your network infrastructure."
- @commit_check
- # TODO: Fix pylint arguments-differ during Job 2.x migration
- def run(self, data, commit): # pylint: disable=too-many-branches,arguments-differ
+ def run(self, **data): # pylint: disable=too-many-branches
"""Run config compliance report script."""
# pylint: disable=unused-argument
- # TODO: Fix pylint no-member during Job 2.x migration
- self.log_debug("Starting compliance job.") # pylint: disable=no-member
+ self.logger.debug("Starting compliance job.")
- # TODO: Fix pylint no-member during Job 2.x migration
- self.log_debug("Refreshing intended configuration git repository.") # pylint: disable=no-member
+ self.logger.debug("Refreshing intended configuration git repository.")
get_refreshed_repos(job_obj=self, repo_type="intended_repository", data=data)
- # TODO: Fix pylint no-member during Job 2.x migration
- self.log_debug("Refreshing backup configuration git repository.") # pylint: disable=no-member
+ self.logger.debug("Refreshing backup configuration git repository.")
get_refreshed_repos(job_obj=self, repo_type="backup_repository", data=data)
- # TODO: Fix pylint no-member during Job 2.x migration
- self.log_debug("Starting config compliance nornir play.") # pylint: disable=no-member
+ self.logger.debug("Starting config compliance nornir play.")
config_compliance(self, data)
@@ -171,34 +150,27 @@ class Meta:
name = "Generate Intended Configurations"
description = "Generate the configuration for your intended state."
- @commit_check
- # TODO: Fix pylint arguments-differ,unused-argument during Job 2.x migration
- def run(self, data, commit): # pylint: disable=arguments-differ,unused-argument
+ def run(self, **data):
"""Run config generation script."""
- # TODO: Fix pylint no-member during Job 2.x migration
- self.log_debug("Starting intended job.") # pylint: disable=no-member
+ self.logger.debug("Starting intended job.")
now = datetime.now()
- # TODO: Fix pylint no-member during Job 2.x migration
- self.log_debug("Pull Jinja template repos.") # pylint: disable=no-member
+ self.logger.debug("Pull Jinja template repos.")
get_refreshed_repos(job_obj=self, repo_type="jinja_repository", data=data)
- # TODO: Fix pylint no-member during Job 2.x migration
- self.log_debug("Pull Intended config repos.") # pylint: disable=no-member
+ self.logger.debug("Pull Intended config repos.")
# Instantiate a GitRepo object for each GitRepository in GoldenConfigSettings.
intended_repos = get_refreshed_repos(job_obj=self, repo_type="intended_repository", data=data)
- # TODO: Fix pylint no-member during Job 2.x migration
- self.log_debug( # pylint: disable=no-member
+ self.logger.debug(
"Building device settings mapping and running intended config nornir play."
)
config_intended(self, data)
# Commit / Push each repo after job is completed.
for intended_repo in intended_repos:
- # TODO: Fix pylint no-member during Job 2.x migration
- self.log_debug(f"Push new intended configs to repo {intended_repo.url}.") # pylint: disable=no-member
+ self.logger.debug(f"Push new intended configs to repo {intended_repo.url}.")
intended_repo.commit_with_added(f"INTENDED CONFIG CREATION JOB - {now}")
intended_repo.push()
@@ -226,30 +198,23 @@ class Meta:
name = "Backup Configurations"
description = "Backup the configurations of your network devices."
- @commit_check
- # TODO: Fix pylint arguments-differ,unused-argument during Job 2.x migration
- def run(self, data, commit): # pylint: disable=arguments-differ,unused-argument
+ def run(self, **data):
"""Run config backup process."""
- # TODO: Fix pylint no-member during Job 2.x migration
- self.log_debug("Starting backup job.") # pylint: disable=no-member
+ self.logger.debug("Starting backup job.")
now = datetime.now()
- # TODO: Fix pylint no-member during Job 2.x migration
- self.log_debug("Pull Backup config repo.") # pylint: disable=no-member
+ self.logger.debug("Pull Backup config repo.")
# Instantiate a GitRepo object for each GitRepository in GoldenConfigSettings.
backup_repos = get_refreshed_repos(job_obj=self, repo_type="backup_repository", data=data)
- # TODO: Fix pylint no-member during Job 2.x migration
- self.log_debug(f"Starting backup jobs to the following repos: {backup_repos}") # pylint: disable=no-member
+ self.logger.debug(f"Starting backup jobs to the following repos: {backup_repos}")
- # TODO: Fix pylint no-member during Job 2.x migration
- self.log_debug("Starting config backup nornir play.") # pylint: disable=no-member
+ self.logger.debug("Starting config backup nornir play.")
config_backup(self, data)
# Commit / Push each repo after job is completed.
for backup_repo in backup_repos:
- # TODO: Fix pylint no-member during Job 2.x migration
- self.log_debug(f"Pushing Backup config repo {backup_repo.url}.") # pylint: disable=no-member
+ self.logger.debug(f"Pushing Backup config repo {backup_repo.url}.")
backup_repo.commit_with_added(f"BACKUP JOB {now}")
backup_repo.push()
@@ -266,9 +231,7 @@ class Meta:
name = "Execute All Golden Configuration Jobs - Single Device"
description = "Process to run all Golden Configuration jobs configured."
- @commit_check
- # TODO: Fix pylint arguments-differ,unused-argument during Job 2.x migration
- def run(self, data, commit): # pylint: disable=arguments-differ,unused-argument
+ def run(self, **data):
"""Run all jobs."""
if ENABLE_INTENDED:
IntendedJob().run.__func__(self, data, True) # pylint: disable=too-many-function-args
@@ -301,9 +264,7 @@ class Meta:
name = "Execute All Golden Configuration Jobs - Multiple Device"
description = "Process to run all Golden Configuration jobs configured against multiple devices."
- @commit_check
- # TODO: Fix pylint arguments-differ,unused-argument during Job 2.x migration
- def run(self, data, commit): # pylint: disable=arguments-differ,unused-argument
+ def run(self, **data):
"""Run all jobs."""
if ENABLE_INTENDED:
IntendedJob().run.__func__(self, data, True) # pylint: disable=too-many-function-args
@@ -319,8 +280,7 @@ class GenerateConfigPlans(Job, FormEntry):
# Device QS Filters
tenant_group = FormEntry.tenant_group
tenant = FormEntry.tenant
- region = FormEntry.region
- site = FormEntry.site
+ location = FormEntry.location
rack_group = FormEntry.rack_group
rack = FormEntry.rack
role = FormEntry.role
@@ -369,9 +329,9 @@ def _validate_inputs(self, data):
self._feature = ComplianceFeature.objects.all()
if self._plan_type in ["manual"]:
if not self._commands:
- self.log_failure("No commands entered for config plan generation.")
- return False
- return True
+ error_msg = "No commands entered for config plan generation."
+ self.logger.error(error_msg)
+ raise ValueError(error_msg)
def _generate_config_plan_from_feature(self):
"""Generate config plans from features."""
@@ -387,7 +347,7 @@ def _generate_config_plan_from_feature(self):
if not config_sets:
_features = ", ".join([str(feat) for feat in self._feature])
- self.log_debug(f"Device `{device}` does not have `{self._plan_type}` configs for `{_features}`.")
+ self.logger.debug(f"Device `{device}` does not have `{self._plan_type}` configs for `{_features}`.")
continue
config_plan = ConfigPlan.objects.create(
device=device,
@@ -401,18 +361,20 @@ def _generate_config_plan_from_feature(self):
config_plan.feature.set(features)
config_plan.validated_save()
_features = ", ".join([str(feat) for feat in features])
- self.log_success(obj=config_plan, message=f"Config plan created for `{device}` with feature `{_features}`.")
+ self.logger.info(obj=config_plan, message=f"Config plan created for `{device}` with feature `{_features}`.")
def _generate_config_plan_from_manual(self):
"""Generate config plans from manual."""
default_context = {
"request": self.request,
- "user": self.request.user,
+ "user": self.user,
}
for device in self._device_qs:
config_set = generate_config_set_from_manual(device, self._commands, context=default_context)
if not config_set:
- self.log_debug(f"Device {self.device} did not return a rendered config set from the provided commands.")
+ self.logger.debug(
+ f"Device {self.device} did not return a rendered config set from the provided commands."
+ )
continue
config_plan = ConfigPlan.objects.create(
device=device,
@@ -423,27 +385,28 @@ def _generate_config_plan_from_manual(self):
status=self._status,
job_result=self.job_result,
)
- self.log_success(obj=config_plan, message=f"Config plan created for {device} with manual commands.")
+ self.logger.info(obj=config_plan, message=f"Config plan created for {device} with manual commands.")
- def run(self, data, commit):
+ def run(self, **data):
"""Run config plan generation process."""
- self.log_debug("Starting config plan generation job.")
- if not self._validate_inputs(data):
- return
+ self.logger.debug("Starting config plan generation job.")
+ self._validate_inputs(data)
try:
self._device_qs = get_job_filter(data)
- except NornirNautobotException as exc:
- self.log_failure(str(exc))
- return
+ except NornirNautobotException as error:
+ error_msg = str(error)
+ self.logger.error(error_msg)
+ raise NornirNautobotException(error_msg)
if self._plan_type in ["intended", "missing", "remediation"]:
- self.log_debug("Starting config plan generation for compliance features.")
+ self.logger.debug("Starting config plan generation for compliance features.")
self._generate_config_plan_from_feature()
elif self._plan_type in ["manual"]:
- self.log_debug("Starting config plan generation for manual commands.")
+ self.logger.debug("Starting config plan generation for manual commands.")
self._generate_config_plan_from_manual()
else:
- self.log_failure(f"Unknown config plan type {self._plan_type}.")
- return
+ error_msg = f"Unknown config plan type {self._plan_type}."
+ self.logger.error()
+ raise ValueError(error_msg)
class DeployConfigPlans(Job):
@@ -458,13 +421,10 @@ class Meta:
name = "Deploy Config Plans"
description = "Deploy config plans to devices."
- def run(self, data, commit):
+ def run(self, **data): # pylint: disable=arguments-differ
"""Run config plan deployment process."""
- self.log_debug("Starting config plan deployment job.")
- config_deployment(self, data, commit)
- if commit and not self.failed:
- config_plan_qs = data["config_plan"]
- config_plan_qs.delete()
+ self.logger.debug("Starting config plan deployment job.")
+ config_deployment(self, **data)
class DeployConfigPlanJobButtonReceiver(JobButtonReceiver):
@@ -477,11 +437,9 @@ class Meta:
def receive_job_button(self, obj):
"""Run config plan deployment process."""
- self.log_debug("Starting config plan deployment job.")
+ self.logger.debug("Starting config plan deployment job.")
data = {"debug": False, "config_plan": ConfigPlan.objects.filter(id=obj.id)}
- config_deployment(self, data, commit=True)
- if not self.failed:
- obj.delete()
+ config_deployment(self, **data=True)
# Conditionally allow jobs based on whether or not turned on.
diff --git a/nautobot_golden_config/management/commands/run_config_backup.py b/nautobot_golden_config/management/commands/run_config_backup.py
deleted file mode 100644
index 348c3949..00000000
--- a/nautobot_golden_config/management/commands/run_config_backup.py
+++ /dev/null
@@ -1,22 +0,0 @@
-"""Add the run_config_backup command to nautobot-server."""
-
-from django.core.management.base import BaseCommand
-from nautobot.extras.jobs import get_job
-
-from nautobot_golden_config.utilities.management import job_runner
-
-
-class Command(BaseCommand):
- """Boilerplate Command to inherit from BaseCommand."""
-
- help = "Run Config Backup from Golden Config Plugin."
-
- def add_arguments(self, parser):
- """Add arguments for run_config_backup."""
- parser.add_argument("-u", "--user", type=str, required=True, help="User to run the Job as.")
- parser.add_argument("-d", "--device", type=str, help="Define a uniquely defined device name")
-
- def handle(self, *args, **kwargs):
- """Add handler for run_config_backup."""
- job_class = get_job("plugins/nautobot_golden_config.jobs/BackupJob")
- job_runner(self, job_class, kwargs.get("device"), kwargs.get("user"))
diff --git a/nautobot_golden_config/management/commands/run_config_compliance.py b/nautobot_golden_config/management/commands/run_config_compliance.py
deleted file mode 100644
index 488ecccc..00000000
--- a/nautobot_golden_config/management/commands/run_config_compliance.py
+++ /dev/null
@@ -1,22 +0,0 @@
-"""Add the run_config_compliance command to nautobot-server."""
-
-from django.core.management.base import BaseCommand
-from nautobot.extras.jobs import get_job
-
-from nautobot_golden_config.utilities.management import job_runner
-
-
-class Command(BaseCommand):
- """Boilerplate Command to inherit from BaseCommand."""
-
- help = "Run Config Compliance Job from Golden Config Plugin."
-
- def add_arguments(self, parser):
- """Add arguments for run_config_compliance."""
- parser.add_argument("-u", "--user", type=str, required=True, help="User to run the Job as.")
- parser.add_argument("-d", "--device", type=str, help="Define a uniquely defined device name")
-
- def handle(self, *args, **kwargs):
- """Add handler for run_config_compliance."""
- job_class = get_job("plugins/nautobot_golden_config.jobs/ComplianceJob")
- job_runner(self, job_class, kwargs.get("device"), kwargs.get("user"))
diff --git a/nautobot_golden_config/management/commands/run_generate_config.py b/nautobot_golden_config/management/commands/run_generate_config.py
deleted file mode 100644
index 68c70c25..00000000
--- a/nautobot_golden_config/management/commands/run_generate_config.py
+++ /dev/null
@@ -1,22 +0,0 @@
-"""Add the run_generate_config command to nautobot-server."""
-
-from django.core.management.base import BaseCommand
-from nautobot.extras.jobs import get_job
-
-from nautobot_golden_config.utilities.management import job_runner
-
-
-class Command(BaseCommand):
- """Boilerplate Command to inherit from BaseCommand."""
-
- help = "Run Job to generate your intended configuration from Golden Config Plugin."
-
- def add_arguments(self, parser):
- """Add arguments for run_generate_config."""
- parser.add_argument("-u", "--user", type=str, required=True, help="User to run the Job as.")
- parser.add_argument("-d", "--device", type=str, help="Define a uniquely defined device name")
-
- def handle(self, *args, **kwargs):
- """Add handler for run_generate_config."""
- job_class = get_job("plugins/nautobot_golden_config.jobs/IntendedJob")
- job_runner(self, job_class, kwargs.get("device"), kwargs.get("user"))
diff --git a/nautobot_golden_config/models.py b/nautobot_golden_config/models.py
index aa41bac4..345c690f 100644
--- a/nautobot_golden_config/models.py
+++ b/nautobot_golden_config/models.py
@@ -7,14 +7,11 @@
from django.core.exceptions import ValidationError
from django.db import models
from django.utils.module_loading import import_string
-from django.utils.text import slugify
from hier_config import Host as HierConfigHost
from nautobot.core.models.generics import PrimaryModel
from nautobot.extras.models import ObjectChange
-from nautobot.extras.models import DynamicGroup, ObjectChange
-from nautobot.extras.models.statuses import StatusField
from nautobot.extras.utils import extras_features
from nautobot.core.models.utils import serialize_object, serialize_object_v2
from netutils.config.compliance import feature_compliance
@@ -225,36 +222,30 @@ def __str__(self):
class ComplianceRule(PrimaryModel): # pylint: disable=too-many-ancestors
"""ComplianceRule details."""
- feature = models.ForeignKey(to="ComplianceFeature", on_delete=models.CASCADE, blank=False, related_name="feature")
+ feature = models.ForeignKey(to="ComplianceFeature", on_delete=models.CASCADE, related_name="feature")
platform = models.ForeignKey(
to="dcim.Platform",
on_delete=models.CASCADE,
related_name="compliance_rules",
- null=False,
- blank=False,
)
description = models.CharField(
max_length=200,
blank=True,
)
config_ordered = models.BooleanField(
- null=False,
- blank=False,
verbose_name="Configured Ordered",
help_text="Whether or not the configuration order matters, such as in ACLs.",
+ default=False,
)
config_remediation = models.BooleanField(
default=False,
- null=False,
- blank=False,
verbose_name="Config Remediation",
help_text="Whether or not the config remediation is executed for this compliance rule.",
)
match_config = models.TextField(
- null=True,
blank=True,
verbose_name="Config to Match",
help_text="The config to match that is matched based on the parent most configuration. E.g.: For CLI `router bgp` or `ntp`. For JSON this is a top level key name.",
@@ -305,18 +296,18 @@ def clean(self):
class ConfigCompliance(PrimaryModel): # pylint: disable=too-many-ancestors
"""Configuration compliance details."""
- device = models.ForeignKey(to="dcim.Device", on_delete=models.CASCADE, help_text="The device", blank=False)
- rule = models.ForeignKey(to="ComplianceRule", on_delete=models.CASCADE, blank=False, related_name="rule")
- compliance = models.BooleanField(null=True, blank=True)
+ device = models.ForeignKey(to="dcim.Device", on_delete=models.CASCADE, help_text="The device")
+ rule = models.ForeignKey(to="ComplianceRule", on_delete=models.CASCADE, related_name="rule")
+ compliance = models.BooleanField(blank=True)
actual = models.JSONField(blank=True, help_text="Actual Configuration for feature")
intended = models.JSONField(blank=True, help_text="Intended Configuration for feature")
# these three are config snippets exposed for the ConfigDeployment.
- remediation = models.JSONField(blank=True, null=True, help_text="Remediation Configuration for the device")
+ remediation = models.JSONField(blank=True, help_text="Remediation Configuration for the device")
missing = models.JSONField(blank=True, help_text="Configuration that should be on the device.")
extra = models.JSONField(blank=True, help_text="Configuration that should not be on the device.")
- ordered = models.BooleanField(default=True)
+ ordered = models.BooleanField(default=False)
# Used for django-pivot, both compliance and compliance_int should be set.
- compliance_int = models.IntegerField(null=True, blank=True)
+ compliance_int = models.IntegerField(blank=True)
def to_objectchange(
self, action, *, related_object=None, object_data_extra=None, object_data_exclude=None
@@ -382,6 +373,7 @@ def save(self, *args, **kwargs):
"""The actual configuration compliance happens here, but the details for actual compliance job would be found in FUNC_MAPPER."""
self.compliance_on_save()
self.remediation_on_save()
+ self.full_clean()
super().save(*args, **kwargs)
@@ -405,16 +397,16 @@ class GoldenConfig(PrimaryModel): # pylint: disable=too-many-ancestors
blank=False,
)
backup_config = models.TextField(blank=True, help_text="Full backup config for device.")
- backup_last_attempt_date = models.DateTimeField(null=True)
- backup_last_success_date = models.DateTimeField(null=True)
+ backup_last_attempt_date = models.DateTimeField(null=True, blank=True)
+ backup_last_success_date = models.DateTimeField(null=True, blank=True)
intended_config = models.TextField(blank=True, help_text="Intended config for the device.")
- intended_last_attempt_date = models.DateTimeField(null=True)
- intended_last_success_date = models.DateTimeField(null=True)
+ intended_last_attempt_date = models.DateTimeField(null=True, blank=True)
+ intended_last_success_date = models.DateTimeField(null=True, blank=True)
compliance_config = models.TextField(blank=True, help_text="Full config diff for device.")
- compliance_last_attempt_date = models.DateTimeField(null=True)
- compliance_last_success_date = models.DateTimeField(null=True)
+ compliance_last_attempt_date = models.DateTimeField(null=True, blank=True)
+ compliance_last_success_date = models.DateTimeField(null=True, blank=True)
def to_objectchange(
self, action, *, related_object=None, object_data_extra=None, object_data_exclude=None
@@ -447,16 +439,16 @@ def __str__(self):
class GoldenConfigSetting(PrimaryModel): # pylint: disable=too-many-ancestors
"""GoldenConfigSetting Model definition. This provides global configs instead of via configs.py."""
- name = models.CharField(max_length=100, unique=True, blank=False)
- slug = models.SlugField(max_length=100, unique=True, blank=False)
- weight = models.PositiveSmallIntegerField(default=1000, blank=False)
+ name = models.CharField(max_length=100, unique=True)
+ slug = models.SlugField(max_length=100, unique=True)
+ weight = models.PositiveSmallIntegerField(default=1000)
description = models.CharField(
max_length=200,
blank=True,
)
backup_repository = models.ForeignKey(
to="extras.GitRepository",
- on_delete=models.SET_NULL,
+ on_delete=models.PROTECT,
null=True,
blank=True,
related_name="backup_repository",
@@ -464,14 +456,13 @@ class GoldenConfigSetting(PrimaryModel): # pylint: disable=too-many-ancestors
)
backup_path_template = models.CharField(
max_length=255,
- null=False,
blank=True,
verbose_name="Backup Path in Jinja Template Form",
help_text="The Jinja path representation of where the backup file will be found. The variable `obj` is available as the device instance object of a given device, as is the case for all Jinja templates. e.g. `{{obj.location.name}}/{{obj.name}}.cfg`",
)
intended_repository = models.ForeignKey(
to="extras.GitRepository",
- on_delete=models.SET_NULL,
+ on_delete=models.PROTECT,
null=True,
blank=True,
related_name="intended_repository",
@@ -479,14 +470,13 @@ class GoldenConfigSetting(PrimaryModel): # pylint: disable=too-many-ancestors
)
intended_path_template = models.CharField(
max_length=255,
- null=False,
blank=True,
verbose_name="Intended Path in Jinja Template Form",
help_text="The Jinja path representation of where the generated file will be places. e.g. `{{obj.location.name}}/{{obj.name}}.cfg`",
)
jinja_repository = models.ForeignKey(
to="extras.GitRepository",
- on_delete=models.SET_NULL,
+ on_delete=models.PROTECT,
null=True,
blank=True,
related_name="jinja_template",
@@ -494,13 +484,11 @@ class GoldenConfigSetting(PrimaryModel): # pylint: disable=too-many-ancestors
)
jinja_path_template = models.CharField(
max_length=255,
- null=False,
blank=True,
verbose_name="Template Path in Jinja Template Form",
help_text="The Jinja path representation of where the Jinja template can be found. e.g. `{{obj.platform.network_driver}}.j2`",
)
backup_test_connectivity = models.BooleanField(
- null=False,
default=True,
verbose_name="Backup Test",
help_text="Whether or not to pretest the connectivity of the device by verifying there is a resolvable IP that can connect to port 22.",
@@ -570,13 +558,11 @@ def get_url_to_filtered_device_list(self):
class ConfigRemove(PrimaryModel): # pylint: disable=too-many-ancestors
"""ConfigRemove for Regex Line Removals from Backup Configuration Model definition."""
- name = models.CharField(max_length=255, null=False, blank=False)
+ name = models.CharField(max_length=255)
platform = models.ForeignKey(
to="dcim.Platform",
on_delete=models.CASCADE,
related_name="backup_line_remove",
- null=False,
- blank=False,
)
description = models.CharField(
max_length=200,
@@ -613,13 +599,11 @@ def __str__(self):
class ConfigReplace(PrimaryModel): # pylint: disable=too-many-ancestors
"""ConfigReplace for Regex Line Replacements from Backup Configuration Model definition."""
- name = models.CharField(max_length=255, null=False, blank=False)
+ name = models.CharField(max_length=255)
platform = models.ForeignKey(
to="dcim.Platform",
on_delete=models.CASCADE,
related_name="backup_line_replace",
- null=False,
- blank=False,
)
description = models.CharField(
max_length=200,
@@ -660,8 +644,6 @@ class RemediationSetting(PrimaryModel): # pylint: disable=too-many-ancestors
to="dcim.Platform",
on_delete=models.CASCADE,
related_name="remediation_settings",
- null=False,
- blank=False,
)
remediation_type = models.CharField(
@@ -697,11 +679,7 @@ def to_csv(self):
def __str__(self):
"""Return a sane string representation of the instance."""
- return str(self.platform.slug)
-
- def get_absolute_url(self):
- """Absolute url for the RemediationRule instance."""
- return reverse("plugins:nautobot_golden_config:remediationsetting", args=[self.pk])
+ return str(self.platform.name)
@extras_features(
@@ -733,19 +711,15 @@ class ConfigPlan(PrimaryModel): # pylint: disable=too-many-ancestors
to="extras.JobResult",
on_delete=models.CASCADE,
related_name="config_plan",
- null=False,
- blank=False,
verbose_name="Job Result",
)
change_control_id = models.CharField(
max_length=50,
blank=True,
- null=True,
verbose_name="Change Control ID",
help_text="Change Control ID for this configuration plan.",
)
change_control_url = models.URLField(blank=True, verbose_name="Change Control URL")
- status = StatusField(blank=True, null=True, on_delete=models.PROTECT)
class Meta:
"""Meta information for ConfigPlan model."""
@@ -755,7 +729,3 @@ class Meta:
def __str__(self):
"""Return a simple string if model is called."""
return f"{self.device.name}-{self.plan_type}-{self.created}"
-
- def get_absolute_url(self):
- """Return absolute URL for instance."""
- return reverse("plugins:nautobot_golden_config:configplan", args=[self.pk])
diff --git a/nautobot_golden_config/navigation.py b/nautobot_golden_config/navigation.py
index 5f6b3d63..befec9c2 100644
--- a/nautobot_golden_config/navigation.py
+++ b/nautobot_golden_config/navigation.py
@@ -1,6 +1,7 @@
"""Add the configuration compliance buttons to the Plugins Navigation."""
-from nautobot.core.apps import NavMenuGroup, NavMenuItem, NavMenuTab
+from nautobot.apps.ui import NavMenuAddButton, NavMenuGroup, NavMenuItem, NavMenuTab
+
from nautobot_golden_config.utilities.constant import ENABLE_COMPLIANCE, ENABLE_BACKUP
items_operate = [
@@ -56,11 +57,8 @@
name="Config Plans",
permissions=["nautobot_golden_config.view_configplan"],
buttons=(
- NavMenuButton(
+ NavMenuAddButton(
link="plugins:nautobot_golden_config:configplan_add",
- title="Generate Config Plan",
- icon_class="mdi mdi-plus-thick",
- button_class=ButtonColorChoices.GREEN,
permissions=["nautobot_golden_config.add_configplan"],
),
),
@@ -93,11 +91,8 @@
name="Remediation Settings",
permissions=["nautobot_golden_config.view_remediationsetting"],
buttons=(
- NavMenuButton(
+ NavMenuAddButton(
link="plugins:nautobot_golden_config:remediationsetting_add",
- title="Remediation Settings",
- icon_class="mdi mdi-plus-thick",
- button_class=ButtonColorChoices.GREEN,
permissions=["nautobot_golden_config.add_remediationsetting"],
),
),
diff --git a/nautobot_golden_config/nornir_plays/config_backup.py b/nautobot_golden_config/nornir_plays/config_backup.py
index 9aec8ec3..c30fbcc4 100644
--- a/nautobot_golden_config/nornir_plays/config_backup.py
+++ b/nautobot_golden_config/nornir_plays/config_backup.py
@@ -7,12 +7,12 @@
from nornir.core.task import Result, Task
from nornir.core.plugins.inventory import InventoryPluginRegister
+from nornir_nautobot.exceptions import NornirNautobotException
from nornir_nautobot.plugins.tasks.dispatcher import dispatcher
from nornir_nautobot.utils.logger import NornirLogger
from nautobot_plugin_nornir.plugins.inventory.nautobot_orm import NautobotORMInventory
from nautobot_plugin_nornir.constants import NORNIR_SETTINGS
-from nautobot_plugin_nornir.utils import get_dispatcher
from nautobot_golden_config.utilities.db_management import close_threaded_db_connections
from nautobot_golden_config.utilities.helper import (
@@ -63,29 +63,31 @@ def run_backup( # pylint: disable=too-many-arguments
if settings.backup_test_connectivity is not False:
task.run(
task=dispatcher,
- name="TEST CONNECTIVITY",
method="check_connectivity",
- obj=obj,
logger=logger,
- default_drivers_mapping=get_dispatcher(),
+ obj=obj,
+ framework="napalm",
+ custom_dispatcher={},
+ name="TEST CONNECTIVITY",
)
running_config = task.run(
task=dispatcher,
- name="SAVE BACKUP CONFIGURATION TO FILE",
method="get_config",
obj=obj,
logger=logger,
+ framework="napalm",
+ custom_dispatcher={},
+ name="SAVE BACKUP CONFIGURATION TO FILE",
backup_file=backup_file,
- remove_lines=remove_regex_dict.get(obj.platform.netwokr_driver, []),
- substitute_lines=replace_regex_dict.get(obj.platform.netwokr_driver, []),
- default_drivers_mapping=get_dispatcher(),
+ remove_lines=remove_regex_dict.get(obj.platform.network_driver, []),
+ substitute_lines=replace_regex_dict.get(obj.platform.network_driver, []),
)[1].result["config"]
backup_obj.backup_last_success_date = task.host.defaults.data["now"]
backup_obj.backup_config = running_config
backup_obj.save()
- logger.log_success(obj, "Successfully extracted running configuration from device.")
+ logger.log_info(obj, "Successfully extracted running configuration from device.")
return Result(host=task.host, result=running_config)
@@ -143,7 +145,8 @@ def config_backup(job_result, data):
logger.log_debug("Completed configuration from devices.")
except Exception as err:
- logger.log_failure(None, err)
- raise
+ error_msg = f"E3001: {err}"
+ logger.log_error(error_msg)
+ raise NornirNautobotException(error_msg)
logger.log_debug("Completed configuration backup job for devices.")
diff --git a/nautobot_golden_config/nornir_plays/config_compliance.py b/nautobot_golden_config/nornir_plays/config_compliance.py
index 63c377c8..66fdb420 100644
--- a/nautobot_golden_config/nornir_plays/config_compliance.py
+++ b/nautobot_golden_config/nornir_plays/config_compliance.py
@@ -38,7 +38,7 @@
def get_rules():
"""A serializer of sorts to return rule mappings as a dictionary."""
- # TODO: Review if creating a proper serializer is the way to go.
+ # TODO: Future: Review if creating a proper serializer is the way to go.
rules = defaultdict(list)
for compliance_rule in ComplianceRule.objects.all():
platform = str(compliance_rule.platform.network_driver)
@@ -64,8 +64,9 @@ def get_config_element(rule, config, obj, logger):
config_json = get_json_config(config)
if not config_json:
- logger.log_failure(obj, "Unable to interpret configuration as JSON.")
- raise NornirNautobotException("Unable to interpret configuration as JSON.")
+ error_msg = "E3002: Unable to interpret configuration as JSON."
+ logger.log_error(error_msg, extra={"object": obj})
+ raise NornirNautobotException(error_msg)
if rule["obj"].match_config:
config_element = {k: config_json.get(k) for k in rule["obj"].match_config.splitlines() if k in config_json}
@@ -74,19 +75,16 @@ def get_config_element(rule, config, obj, logger):
elif rule["obj"].config_type == ComplianceRuleConfigTypeChoice.TYPE_CLI:
if get_platform(obj.platform.network_driver) not in parser_map.keys():
- logger.log_failure(
- obj,
- f"There is currently no CLI-config parser support for platform network_driver `{get_platform(obj.platform.network_driver)}`, preemptively failed.",
- )
- raise NornirNautobotException(
- f"There is currently no CLI-config parser support for platform network_driver `{get_platform(obj.platform.network_driver)}`, preemptively failed."
- )
+ error_msg = f"E3003: There is currently no CLI-config parser support for platform network_driver `{get_platform(obj.platform.network_driver)}`, preemptively failed."
+ logger.log_error(error_msg, extra={"object": obj})
+ raise NornirNautobotException(error_msg)
config_element = section_config(rule, config, get_platform(obj.platform.network_driver))
else:
- logger.log_failure(obj, f"There rule type ({rule['obj'].config_type}) is not recognized.")
- raise NornirNautobotException(f"There rule type ({rule['obj'].config_type}) is not recognized.")
+ error_msg = f"E3004: There rule type ({rule['obj'].config_type}) is not recognized."
+ logger.log_error(error_msg, extra={"object": obj})
+ raise NornirNautobotException(error_msg)
return config_element
@@ -129,28 +127,26 @@ def run_compliance( # pylint: disable=too-many-arguments,too-many-locals
intended_file = os.path.join(intended_directory, intended_path_template_obj)
if not os.path.exists(intended_file):
- logger.log_failure(obj, f"Unable to locate intended file for device at {intended_file}, preemptively failed.")
- raise NornirNautobotException(
- f"Unable to locate intended file for device at {intended_file}, preemptively failed."
- )
+ error_msg = f"E3005: Unable to locate intended file for device at {intended_file}, preemptively failed."
+ logger.log_error(error_msg, extra={"object": obj})
+ raise NornirNautobotException(error_msg)
backup_directory = settings.backup_repository.filesystem_path
backup_template = render_jinja_template(obj, logger, settings.backup_path_template)
backup_file = os.path.join(backup_directory, backup_template)
if not os.path.exists(backup_file):
- logger.log_failure(obj, f"Unable to locate backup file for device at {backup_file}, preemptively failed.")
- raise NornirNautobotException(f"Unable to locate backup file for device at {backup_file}, preemptively failed.")
+ error_msg = f"E3006: Unable to locate backup file for device at {backup_file}, preemptively failed."
+ logger.log_error(error_msg, extra={"object": obj})
+ raise NornirNautobotException(error_msg)
platform = obj.platform.network_driver
if not rules.get(platform):
- logger.log_failure(
- obj,
- f"There is no defined `Configuration Rule` for platform network_driver `{platform}`, preemptively failed.",
- )
- raise NornirNautobotException(
- f"There is no defined `Configuration Rule` for platform network_driver `{platform}`, preemptively failed."
+ error_msg = (
+ f"E3007: There is no defined `Configuration Rule` for platform network_driver `{platform}`, preemptively failed."
)
+ logger.log_error(error_msg, extra={"object": obj})
+ raise NornirNautobotException(error_msg)
backup_cfg = _open_file_config(backup_file)
intended_cfg = _open_file_config(intended_file)
@@ -174,7 +170,7 @@ def run_compliance( # pylint: disable=too-many-arguments,too-many-locals
compliance_obj.compliance_last_success_date = task.host.defaults.data["now"]
compliance_obj.compliance_config = "\n".join(diff_files(backup_file, intended_file))
compliance_obj.save()
- logger.log_success(obj, "Successfully tested compliance job.")
+ logger.log_info(obj, "Successfully tested compliance job.")
return Result(host=task.host)
@@ -218,7 +214,8 @@ def config_compliance(job_result, data):
)
except Exception as err:
- logger.log_failure(None, err)
- raise
+ error_msg = f"E3009: {err}"
+ logger.log_error(error_msg)
+ raise NornirNautobotException(error_msg)
logger.log_debug("Completed compliance job for devices.")
diff --git a/nautobot_golden_config/nornir_plays/config_deployment.py b/nautobot_golden_config/nornir_plays/config_deployment.py
index 1862f7ea..a563dde3 100644
--- a/nautobot_golden_config/nornir_plays/config_deployment.py
+++ b/nautobot_golden_config/nornir_plays/config_deployment.py
@@ -1,15 +1,19 @@
"""Nornir job for deploying configurations."""
from datetime import datetime
-from nautobot.dcim.models import Device
-from nautobot_plugin_nornir.plugins.inventory.nautobot_orm import NautobotORMInventory
-from nautobot_plugin_nornir.constants import NORNIR_SETTINGS
-from nautobot_plugin_nornir.utils import get_dispatcher
+
from nornir import InitNornir
from nornir.core.task import Result, Task
from nornir.core.plugins.inventory import InventoryPluginRegister
+
+from nornir_nautobot.exceptions import NornirNautobotException
from nornir_nautobot.plugins.tasks.dispatcher import dispatcher
from nornir_nautobot.utils.logger import NornirLogger
+from nautobot.dcim.models import Device
+
+from nautobot_plugin_nornir.plugins.inventory.nautobot_orm import NautobotORMInventory
+from nautobot_plugin_nornir.constants import NORNIR_SETTINGS
+
from nautobot_golden_config.nornir_plays.processor import ProcessGoldenConfig
InventoryPluginRegister.register("nautobot-inventory", NautobotORMInventory)
@@ -21,20 +25,20 @@ def run_deployment(task: Task, logger: NornirLogger, commit: bool, config_plan_q
plans_to_deploy = config_plan_qs.filter(device=obj)
consolidated_config_set = "\n".join(plans_to_deploy.values_list("config_set", flat=True))
logger.log_debug(f"Consolidated config set: {consolidated_config_set}")
- # TODO: We should add post-processing rendering here
+ # TODO: Future: We should add post-processing rendering here
# after https://github.com/nautobot/nautobot-plugin-golden-config/issues/443
if commit:
result = task.run(
task=dispatcher,
- name="DEPLOY CONFIG TO DEVICE",
method="merge_config",
obj=obj,
logger=logger,
+ custom_dispatcher={},
+ name="DEPLOY CONFIG TO DEVICE",
config=consolidated_config_set,
- default_drivers_mapping=get_dispatcher(),
)[1].result["result"]
- logger.log_success(obj=obj, message="Successfully deployed configuration to device.")
+ logger.log_info(obj=obj, message="Successfully deployed configuration to device.")
else:
result = None
logger.log_info(obj=obj, message="Commit not enabled. Configuration not deployed to device.")
@@ -50,11 +54,10 @@ def config_deployment(job_result, data, commit):
config_plan_qs = data["config_plan"]
if config_plan_qs.filter(status__slug="not-approved").exists():
- logger.log_failure(
- obj=None,
- message="Cannot deploy configuration(s). One or more config plans are not approved.",
- )
- raise ValueError("Cannot deploy configuration(s). One or more config plans are not approved.")
+ error_msg = "E3010: Cannot deploy configuration(s). One or more config plans are not approved."
+ logger.log_error(error_msg)
+ raise NornirNautobotException(error_msg)
+
device_qs = Device.objects.filter(config_plan__in=config_plan_qs).distinct()
try:
@@ -81,7 +84,8 @@ def config_deployment(job_result, data, commit):
config_plan_qs=config_plan_qs,
)
except Exception as err:
- logger.log_failure(obj=None, message=f"Failed to initialize Nornir: {err}")
- raise
+ error_msg = f"E3011: {err}"
+ logger.log_error(error_msg)
+ raise NornirNautobotException(error_msg)
logger.log_debug("Completed configuration deployment.")
diff --git a/nautobot_golden_config/nornir_plays/config_intended.py b/nautobot_golden_config/nornir_plays/config_intended.py
index be5f18e9..8835d8c5 100644
--- a/nautobot_golden_config/nornir_plays/config_intended.py
+++ b/nautobot_golden_config/nornir_plays/config_intended.py
@@ -17,7 +17,6 @@
from nautobot_plugin_nornir.plugins.inventory.nautobot_orm import NautobotORMInventory
from nautobot_plugin_nornir.constants import NORNIR_SETTINGS
-from nautobot_plugin_nornir.utils import get_dispatcher
from nautobot_golden_config.utilities.constant import PLUGIN_CFG
from nautobot_golden_config.utilities.db_management import close_threaded_db_connections
@@ -75,10 +74,10 @@ def run_template( # pylint: disable=too-many-arguments
jinja_template = render_jinja_template(obj, logger, settings.jinja_path_template)
status, device_data = graph_ql_query(nautobot_job.request, obj, settings.sot_agg_query.query)
if status != 200:
- logger.log_failure(obj, f"The GraphQL query return a status of {str(status)} with error of {str(device_data)}")
- raise NornirNautobotException(
- f"The GraphQL query return a status of {str(status)} with error of {str(device_data)}"
- )
+ error_msg = f"E3012: The GraphQL query return a status of {str(status)} with error of {str(device_data)}"
+ logger.log_error(error_msg, extra={"object": obj})
+ raise NornirNautobotException(error_msg)
+
task.host.data.update(device_data)
generated_config = task.run(
@@ -90,7 +89,7 @@ def run_template( # pylint: disable=too-many-arguments
jinja_template=jinja_template,
jinja_root_path=settings.jinja_repository.filesystem_path,
output_file_location=output_file_location,
- default_drivers_mapping=get_dispatcher(),
+ custom_dispatcher={},
jinja_filters=jinja_env.filters,
jinja_env=jinja_env,
)[1].result["config"]
@@ -98,7 +97,7 @@ def run_template( # pylint: disable=too-many-arguments
intended_obj.intended_config = generated_config
intended_obj.save()
- logger.log_success(obj, "Successfully generated the intended configuration.")
+ logger.log_info(obj, "Successfully generated the intended configuration.")
return Result(host=task.host, result=generated_config)
@@ -151,5 +150,6 @@ def config_intended(nautobot_job, data):
)
except Exception as err:
- logger.log_failure(None, err)
- raise
+ error_msg = f"E3013: {err}"
+ logger.log_error(error_msg)
+ raise NornirNautobotException(error_msg)
diff --git a/nautobot_golden_config/nornir_plays/processor.py b/nautobot_golden_config/nornir_plays/processor.py
index fa4afdeb..1f2c1c41 100644
--- a/nautobot_golden_config/nornir_plays/processor.py
+++ b/nautobot_golden_config/nornir_plays/processor.py
@@ -32,4 +32,5 @@ def task_instance_completed(self, task: Task, host: Host, result: MultiResult) -
for level_2_result in level_1_result.exception.result:
if isinstance(level_2_result.exception, NornirNautobotException):
return
- self.logger.log_failure(task.host.data["obj"], f"{task.name} failed: {result.exception}")
+ self.logger.log_error(f"{task.name} failed: {result.exception}", extra={"object": task.host.data["obj"]})
+ # TODO 2.0: update the state????
diff --git a/nautobot_golden_config/templates/nautobot_golden_config/compliance_device_report.html b/nautobot_golden_config/templates/nautobot_golden_config/compliance_device_report.html
index caf6dc3c..d2184c63 100644
--- a/nautobot_golden_config/templates/nautobot_golden_config/compliance_device_report.html
+++ b/nautobot_golden_config/templates/nautobot_golden_config/compliance_device_report.html
@@ -188,7 +188,7 @@