From 2e39e7d50793429d1d4fad140e2b3742d86eafc2 Mon Sep 17 00:00:00 2001 From: Nautobot-Bot <79372327+nautobot-bot@users.noreply.github.com> Date: Wed, 14 Aug 2024 16:49:37 -0400 Subject: [PATCH] Cookie updated by NetworkToCode Cookie Drift Manager Tool (#788) * Cookie updated by NetworkToCode Cookie Drift Manager Tool Template: ``` { "template": "https://github.com/nautobot/cookiecutter-nautobot-app.git", "dir": "nautobot-app", "ref": "refs/tags/nautobot-app-v2.3.0", "path": null } ``` Cookie: ``` { "remote": "https://github.com/nautobot/nautobot-app-golden-config.git", "path": "/tmp/tmpl8jjmwv0/nautobot-app-golden-config", "repository_path": "/tmp/tmpl8jjmwv0/nautobot-app-golden-config", "dir": "", "branch_prefix": "drift-manager", "context": { "codeowner_github_usernames": "@itdependsnetworks @jeffkala @nkallergis", "full_name": "Network to Code, LLC", "email": "opensource@networktocode.com", "github_org": "nautobot", "app_name": "nautobot_golden_config", "verbose_name": "Golden Config", "app_slug": "nautobot-golden-config", "project_slug": "nautobot-app-golden-config", "repo_url": "https://github.com/nautobot/nautobot-app-golden-config", "base_url": "golden-config", "min_nautobot_version": "2.0.0", "max_nautobot_version": "2.9999", "camel_name": "NautobotGoldenConfig", "project_short_description": "An app for configuration on nautobot", "model_class_name": "None", "open_source_license": "Apache-2.0", "docs_base_url": "https://docs.nautobot.com", "docs_app_url": "https://docs.nautobot.com/projects/golden-config/en/latest", "_template": "https://github.com/nautobot/cookiecutter-nautobot-app.git", "_output_dir": "/tmp/tmpl8jjmwv0", "_repo_dir": "/github/home/.cookiecutters/cookiecutter-nautobot-app/nautobot-app", "_checkout": "refs/tags/nautobot-app-v2.3.0" }, "base_branch": "develop", "remote_name": "origin", "pull_request_strategy": "PullRequestStrategy.CREATE", "post_actions": [ "PostAction.BLACK" ], "baked_commit_ref": "78686cacfb51540281444ff112705b91304f797c", "draft": true } ``` CLI Arguments: ``` { "cookie_dir": "", "input": false, "json_filename": "", "output_dir": "", "push": true, "template": "", "template_dir": "", "template_ref": "refs/tags/nautobot-app-v2.3.0", "pull_request": null, "post_action": [], "disable_post_actions": false, "draft": null } ``` * changelog, remove bandit, fix some ruff failures * pylint * revert ruff rule ignore removal * fix tests * fix a bunch of failing tests * Update docs/requirements.txt * Update pyproject.toml * update lockfile --------- Co-authored-by: bakebot Co-authored-by: Gary Snider <75227981+gsnider2195@users.noreply.github.com> --- .bandit.yml | 7 - .cookiecutter.json | 4 +- .dockerignore | 1 - .github/workflows/ci.yml | 36 +-- .pydocstyle.ini | 11 - README.md | 2 +- changes/788.housekeeping | 1 + development/app_config_schema.py | 1 + development/docker-compose.base.yml | 1 - development/docker-compose.dev.yml | 1 - development/docker-compose.mysql.yml | 4 +- development/docker-compose.postgres.yml | 2 - development/docker-compose.redis.yml | 1 - development/nautobot_config.py | 12 +- docs/assets/extra.css | 2 +- docs/dev/contributing.md | 2 +- docs/dev/dev_environment.md | 10 +- docs/requirements.txt | 2 +- mkdocs.yml | 3 +- nautobot_golden_config/__init__.py | 3 +- nautobot_golden_config/api/serializers.py | 8 +- nautobot_golden_config/api/views.py | 23 +- nautobot_golden_config/choices.py | 1 + nautobot_golden_config/datasources.py | 1 + nautobot_golden_config/filters.py | 3 +- nautobot_golden_config/forms.py | 1 - nautobot_golden_config/jobs.py | 2 + nautobot_golden_config/metrics.py | 1 + .../migrations/0001_initial.py | 5 +- .../migrations/0003_auto_20210510_2356.py | 2 +- .../migrations/0004_auto_20210616_2234.py | 2 +- .../migrations/0005_json_compliance_rule.py | 3 +- .../0009_multiple_gc_settings_part_1.py | 2 +- .../0011_multiple_gc_settings_part_3.py | 2 +- .../0019_convert_dynamicgroup_part_1.py | 2 +- .../0020_convert_dynamicgroup_part_2.py | 1 - .../0024_convert_custom_compliance_rules.py | 1 + .../migrations/0027_auto_20230915_1657.py | 2 +- .../0028_auto_20230916_1712_part2.py | 2 +- .../0030_alter_goldenconfig_device.py | 2 +- nautobot_golden_config/models.py | 11 +- .../nornir_plays/config_backup.py | 1 + .../nornir_plays/config_compliance.py | 9 +- .../nornir_plays/config_deployment.py | 21 +- .../nornir_plays/config_intended.py | 8 +- .../nornir_plays/processor.py | 1 + nautobot_golden_config/signals.py | 3 +- nautobot_golden_config/tables.py | 3 +- nautobot_golden_config/template_content.py | 2 + .../templatetags/json_helpers.py | 1 + nautobot_golden_config/tests/conftest.py | 14 +- .../forms/test_golden_config_settings.py | 9 +- nautobot_golden_config/tests/test_api.py | 7 +- nautobot_golden_config/tests/test_basic.py | 9 +- .../tests/test_datasources.py | 3 +- nautobot_golden_config/tests/test_filters.py | 92 +++--- nautobot_golden_config/tests/test_graphql.py | 12 +- nautobot_golden_config/tests/test_helpers.py | 13 +- nautobot_golden_config/tests/test_jobs.py | 9 +- .../test_config_compliance.py | 7 +- .../tests/test_utilities/test_config_plan.py | 5 +- .../tests/test_utilities/test_git.py | 2 +- .../tests/test_utilities/test_graphql.py | 3 +- .../tests/test_utilities/test_helpers.py | 5 +- nautobot_golden_config/tests/test_views.py | 84 +++--- nautobot_golden_config/urls.py | 5 +- .../utilities/config_plan.py | 1 + .../utilities/config_postprocessing.py | 2 + nautobot_golden_config/utilities/constant.py | 1 + .../utilities/db_management.py | 2 +- nautobot_golden_config/utilities/git.py | 1 + nautobot_golden_config/utilities/helper.py | 22 +- nautobot_golden_config/utilities/logger.py | 2 +- nautobot_golden_config/utilities/mat_plot.py | 6 +- nautobot_golden_config/utilities/utils.py | 3 +- nautobot_golden_config/views.py | 4 +- poetry.lock | 275 ++++-------------- pyproject.toml | 48 +-- tasks.py | 117 ++++---- 79 files changed, 374 insertions(+), 621 deletions(-) delete mode 100644 .bandit.yml delete mode 100644 .pydocstyle.ini create mode 100644 changes/788.housekeeping diff --git a/.bandit.yml b/.bandit.yml deleted file mode 100644 index 78140801..00000000 --- a/.bandit.yml +++ /dev/null @@ -1,7 +0,0 @@ ---- -# No need to check for security issues in the test scripts! -exclude_dirs: - - "./nautobot_golden_config/tests/" - - "./.venv/" -skips: - - "B404" diff --git a/.cookiecutter.json b/.cookiecutter.json index f3383d34..f378a64e 100644 --- a/.cookiecutter.json +++ b/.cookiecutter.json @@ -21,7 +21,7 @@ "_drift_manager": { "template": "https://github.com/nautobot/cookiecutter-nautobot-app.git", "template_dir": "nautobot-app", - "template_ref": "refs/tags/nautobot-app-v2.2.1", + "template_ref": "refs/tags/nautobot-app-v2.3.0", "cookie_dir": "", "branch_prefix": "drift-manager", "pull_request_strategy": "create", @@ -29,7 +29,7 @@ "black" ], "draft": true, - "baked_commit_ref": "78686cacfb51540281444ff112705b91304f797c" + "baked_commit_ref": "4eb0e2da920ce7f21715b4dd11549ac433eba529" } } } diff --git a/.dockerignore b/.dockerignore index 2270f496..a0bf06f4 100644 --- a/.dockerignore +++ b/.dockerignore @@ -19,7 +19,6 @@ FAQ.md .git/ .gitignore .github -tasks.py LICENSE **/*.log **/.vscode/ diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a02a72ee..24b94334 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -17,7 +17,7 @@ env: APP_NAME: "nautobot-app-golden-config" jobs: - black: + ruff-format: runs-on: "ubuntu-22.04" env: INVOKE_NAUTOBOT_GOLDEN_CONFIG_LOCAL: "True" @@ -26,20 +26,9 @@ jobs: uses: "actions/checkout@v4" - name: "Setup environment" uses: "networktocode/gh-action-setup-poetry-environment@v6" - - name: "Linting: black" - run: "poetry run invoke black" - bandit: - runs-on: "ubuntu-22.04" - env: - INVOKE_NAUTOBOT_GOLDEN_CONFIG_LOCAL: "True" - steps: - - name: "Check out repository code" - uses: "actions/checkout@v4" - - name: "Setup environment" - uses: "networktocode/gh-action-setup-poetry-environment@v6" - - name: "Linting: bandit" - run: "poetry run invoke bandit" - ruff: + - name: "Linting: ruff format" + run: "poetry run invoke ruff --action format" + ruff-lint: runs-on: "ubuntu-22.04" env: INVOKE_NAUTOBOT_GOLDEN_CONFIG_LOCAL: "True" @@ -61,17 +50,6 @@ jobs: uses: "networktocode/gh-action-setup-poetry-environment@v6" - name: "Check Docs Build" run: "poetry run invoke build-and-check-docs" - flake8: - runs-on: "ubuntu-22.04" - env: - INVOKE_NAUTOBOT_GOLDEN_CONFIG_LOCAL: "True" - steps: - - name: "Check out repository code" - uses: "actions/checkout@v4" - - name: "Setup environment" - uses: "networktocode/gh-action-setup-poetry-environment@v6" - - name: "Linting: flake8" - run: "poetry run invoke flake8" poetry: runs-on: "ubuntu-22.04" env: @@ -96,12 +74,10 @@ jobs: run: "poetry run invoke yamllint" check-in-docker: needs: - - "bandit" - - "ruff" - - "flake8" + - "ruff-format" + - "ruff-lint" - "poetry" - "yamllint" - - "black" runs-on: "ubuntu-22.04" strategy: fail-fast: true diff --git a/.pydocstyle.ini b/.pydocstyle.ini deleted file mode 100644 index 951011dd..00000000 --- a/.pydocstyle.ini +++ /dev/null @@ -1,11 +0,0 @@ -[pydocstyle] -convention = google -inherit = false -match = (?!__init__).*\.py -match-dir = (?!tests|migrations)[^\.].* -# D212 is enabled by default in google convention, and complains if we have a docstring like: -# """ -# My docstring is on the line after the opening quotes instead of on the same line as them. -# """ -# We've discussed and concluded that we consider this to be a valid style choice. -add_ignore = D212, D417 \ No newline at end of file diff --git a/README.md b/README.md index f71cd292..e15eba61 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@
- An App for Nautobot. + An App for Nautobot.

## Overview diff --git a/changes/788.housekeeping b/changes/788.housekeeping new file mode 100644 index 00000000..321e1e26 --- /dev/null +++ b/changes/788.housekeeping @@ -0,0 +1 @@ +Rebaked from the cookie `nautobot-app-v2.3.0`. diff --git a/development/app_config_schema.py b/development/app_config_schema.py index 47009954..a779b14e 100644 --- a/development/app_config_schema.py +++ b/development/app_config_schema.py @@ -1,4 +1,5 @@ """App Config Schema Generator and Validator.""" + import json from importlib import import_module from os import getenv diff --git a/development/docker-compose.base.yml b/development/docker-compose.base.yml index e13ccec3..0b6ac1a2 100644 --- a/development/docker-compose.base.yml +++ b/development/docker-compose.base.yml @@ -13,7 +13,6 @@ x-nautobot-base: &nautobot-base - "creds.env" tty: true -version: "3.8" services: nautobot: depends_on: diff --git a/development/docker-compose.dev.yml b/development/docker-compose.dev.yml index 0e4b0386..3a7ec4ff 100644 --- a/development/docker-compose.dev.yml +++ b/development/docker-compose.dev.yml @@ -3,7 +3,6 @@ # any override will need to include these volumes to use them. # see: https://github.com/docker/compose/issues/3729 --- -version: "3.8" services: nautobot: command: "nautobot-server runserver 0.0.0.0:8080" diff --git a/development/docker-compose.mysql.yml b/development/docker-compose.mysql.yml index e1494749..dbe31cba 100644 --- a/development/docker-compose.mysql.yml +++ b/development/docker-compose.mysql.yml @@ -1,6 +1,4 @@ --- -version: "3.8" - services: nautobot: environment: @@ -18,6 +16,8 @@ services: - "development_mysql.env" db: image: "mysql:8" + command: + - "--max_connections=1000" env_file: - "development.env" - "creds.env" diff --git a/development/docker-compose.postgres.yml b/development/docker-compose.postgres.yml index 12d1de31..8d96fdba 100644 --- a/development/docker-compose.postgres.yml +++ b/development/docker-compose.postgres.yml @@ -1,6 +1,4 @@ --- -version: "3.8" - services: nautobot: environment: diff --git a/development/docker-compose.redis.yml b/development/docker-compose.redis.yml index 6da9fa01..b5e266a3 100644 --- a/development/docker-compose.redis.yml +++ b/development/docker-compose.redis.yml @@ -1,5 +1,4 @@ --- -version: "3.8" services: redis: image: "redis:6-alpine" diff --git a/development/nautobot_config.py b/development/nautobot_config.py index 776bcd4e..49a529dc 100644 --- a/development/nautobot_config.py +++ b/development/nautobot_config.py @@ -1,4 +1,5 @@ """Nautobot development configuration file.""" + import os import sys @@ -9,7 +10,7 @@ # Debug # -DEBUG = is_truthy(os.getenv("NAUTOBOT_DEBUG", False)) +DEBUG = is_truthy(os.getenv("NAUTOBOT_DEBUG", "false")) _TESTING = len(sys.argv) > 1 and sys.argv[1] == "test" if DEBUG and not _TESTING: @@ -47,9 +48,10 @@ "PASSWORD": os.getenv("NAUTOBOT_DB_PASSWORD", ""), # Database password "HOST": os.getenv("NAUTOBOT_DB_HOST", "localhost"), # Database server "PORT": os.getenv( - "NAUTOBOT_DB_PORT", default_db_settings[nautobot_db_engine]["NAUTOBOT_DB_PORT"] + "NAUTOBOT_DB_PORT", + default_db_settings[nautobot_db_engine]["NAUTOBOT_DB_PORT"], ), # Database port, default to postgres - "CONN_MAX_AGE": int(os.getenv("NAUTOBOT_DB_TIMEOUT", 300)), # Database timeout + "CONN_MAX_AGE": int(os.getenv("NAUTOBOT_DB_TIMEOUT", "300")), # Database timeout "ENGINE": nautobot_db_engine, } } @@ -158,8 +160,8 @@ "postprocessing_subscribed": os.environ.get("POSTPROCESSING_SUBSCRIBED", []), "jinja_env": { "undefined": "jinja2.StrictUndefined", - "trim_blocks": is_truthy(os.getenv("NAUTOBOT_JINJA_ENV_TRIM_BLOCKS", True)), - "lstrip_blocks": is_truthy(os.getenv("NAUTOBOT_JINJA_ENV_LSTRIP_BLOCKS", False)), + "trim_blocks": is_truthy(os.getenv("NAUTOBOT_JINJA_ENV_TRIM_BLOCKS", "true")), + "lstrip_blocks": is_truthy(os.getenv("NAUTOBOT_JINJA_ENV_LSTRIP_BLOCKS", "false")), }, # "get_custom_compliance": "my.custom_compliance.func", # "default_deploy_status": "Not Approved", diff --git a/docs/assets/extra.css b/docs/assets/extra.css index 1eff1192..3f3931a0 100644 --- a/docs/assets/extra.css +++ b/docs/assets/extra.css @@ -96,7 +96,7 @@ a.autorefs-external:hover::after { } -/* Customization for mkdocs-version-annotations */ +/* Customization for markdown-version-annotations */ :root { /* Icon for "version-added" admonition: Material Design Icons "plus-box-outline" */ --md-admonition-icon--version-added: url('data:image/svg+xml;charset=utf-8,'); diff --git a/docs/dev/contributing.md b/docs/dev/contributing.md index ccd3a5a6..5bf960b8 100644 --- a/docs/dev/contributing.md +++ b/docs/dev/contributing.md @@ -4,7 +4,7 @@ The project is packaged with a light [development environment](dev_environment.m The project is following Network to Code software development guidelines and is leveraging the following: -- Python linting and formatting: `black`, `pylint`, `bandit`, `flake8`, and `ruff`. +- Python linting and formatting: `pylint` and `ruff`. - YAML linting is done with `yamllint`. - Django unit test to ensure the app is working properly. diff --git a/docs/dev/dev_environment.md b/docs/dev/dev_environment.md index 95837814..50408bcd 100644 --- a/docs/dev/dev_environment.md +++ b/docs/dev/dev_environment.md @@ -123,10 +123,7 @@ Each command can be executed with `invoke `. All commands support the a #### Testing ``` - bandit Run bandit to validate basic static code security analysis. - black Run black to check that Python files adhere to its style standards. - flake8 Run flake8 to check that Python files adhere to its style standards. - ruff Run ruff to validate docstring formatting adheres to NTC defined standards. + ruff Run ruff to perform code formatting and/or linting. pylint Run pylint code analysis. tests Run all tests for this app. unittest Run Django unit tests for the app. @@ -454,7 +451,7 @@ This is the same as running: ### Tests -To run tests against your code, you can run all of the tests that TravisCI runs against any new PR with: +To run tests against your code, you can run all of the tests that the CI runs against any new PR with: ```bash ➜ invoke tests @@ -464,9 +461,6 @@ To run an individual test, you can run any or all of the following: ```bash ➜ invoke unittest -➜ invoke bandit -➜ invoke black -➜ invoke flake8 ➜ invoke ruff ➜ invoke pylint ``` diff --git a/docs/requirements.txt b/docs/requirements.txt index 4a079cd3..3359c083 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1,5 +1,5 @@ mkdocs==1.5.2 mkdocs-material==9.2.4 -mkdocs-version-annotations==1.0.0 +markdown-version-annotations==1.0.1 mkdocstrings-python==1.5.2 mkdocstrings==0.22.0 diff --git a/mkdocs.yml b/mkdocs.yml index 078b57dd..8e2e188e 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -72,6 +72,8 @@ extra: link: "https://twitter.com/networktocode" name: "Network to Code Twitter" markdown_extensions: + - "markdown_version_annotations": + admonition_tag: "???" - "admonition" - "toc": permalink: true @@ -89,7 +91,6 @@ markdown_extensions: - "footnotes" plugins: - "search" - - "mkdocs-version-annotations" - "mkdocstrings": default_handler: "python" handlers: diff --git a/nautobot_golden_config/__init__.py b/nautobot_golden_config/__init__.py index 537cf109..43c6532d 100644 --- a/nautobot_golden_config/__init__.py +++ b/nautobot_golden_config/__init__.py @@ -1,4 +1,5 @@ """App declaration for nautobot_golden_config.""" + # Metadata is inherited from Nautobot. If not including Nautobot in the environment, this should be added from importlib import metadata @@ -71,8 +72,8 @@ def ready(self): # pylint: disable=import-outside-toplevel from .signals import ( config_compliance_platform_cleanup, - post_migrate_create_statuses, post_migrate_create_job_button, + post_migrate_create_statuses, ) nautobot_database_ready.connect(post_migrate_create_statuses, sender=self) diff --git a/nautobot_golden_config/api/serializers.py b/nautobot_golden_config/api/serializers.py index 6e3a8f3e..b9787afb 100644 --- a/nautobot_golden_config/api/serializers.py +++ b/nautobot_golden_config/api/serializers.py @@ -1,13 +1,11 @@ """REST API serializer capabilities for graphql app.""" # pylint: disable=too-many-ancestors -from rest_framework import serializers - -from nautobot.extras.api.mixins import TaggedModelSerializerMixin +from nautobot.core.api.serializers import NautobotModelSerializer from nautobot.dcim.api.serializers import DeviceSerializer from nautobot.dcim.models import Device -from nautobot.core.api.serializers import NautobotModelSerializer - +from nautobot.extras.api.mixins import TaggedModelSerializerMixin +from rest_framework import serializers from nautobot_golden_config import models from nautobot_golden_config.utilities.config_postprocessing import get_config_postprocessing diff --git a/nautobot_golden_config/api/views.py b/nautobot_golden_config/api/views.py index 101dce49..1a7910c9 100644 --- a/nautobot_golden_config/api/views.py +++ b/nautobot_golden_config/api/views.py @@ -1,29 +1,26 @@ """View for Golden Config APIs.""" + import json from django.contrib.contenttypes.models import ContentType - -from rest_framework.mixins import DestroyModelMixin, ListModelMixin, RetrieveModelMixin, UpdateModelMixin -from rest_framework.views import APIView -from rest_framework.viewsets import GenericViewSet -from rest_framework.response import Response -from rest_framework.routers import APIRootView -from rest_framework.permissions import AllowAny, IsAuthenticated, BasePermission -from rest_framework import mixins, viewsets - from nautobot.core.api.views import ( BulkDestroyModelMixin, BulkUpdateModelMixin, ModelViewSetMixin, NautobotAPIVersionMixin, ) -from nautobot.extras.api.views import NautobotModelViewSet, NotesViewSetMixin from nautobot.dcim.models import Device +from nautobot.extras.api.views import NautobotModelViewSet, NotesViewSetMixin +from rest_framework import mixins, viewsets +from rest_framework.mixins import DestroyModelMixin, ListModelMixin, RetrieveModelMixin, UpdateModelMixin +from rest_framework.permissions import AllowAny, BasePermission, IsAuthenticated +from rest_framework.response import Response +from rest_framework.routers import APIRootView +from rest_framework.views import APIView +from rest_framework.viewsets import GenericViewSet - +from nautobot_golden_config import filters, models from nautobot_golden_config.api import serializers -from nautobot_golden_config import models -from nautobot_golden_config import filters from nautobot_golden_config.utilities.graphql import graph_ql_query from nautobot_golden_config.utilities.helper import get_device_to_settings_map diff --git a/nautobot_golden_config/choices.py b/nautobot_golden_config/choices.py index 7531b279..0d7f1137 100644 --- a/nautobot_golden_config/choices.py +++ b/nautobot_golden_config/choices.py @@ -1,4 +1,5 @@ """Choicesets for golden config.""" + from nautobot.core.choices import ChoiceSet diff --git a/nautobot_golden_config/datasources.py b/nautobot_golden_config/datasources.py index c8953736..07b2e837 100644 --- a/nautobot_golden_config/datasources.py +++ b/nautobot_golden_config/datasources.py @@ -1,4 +1,5 @@ """Data source app extension to register additional git repo types.""" + import os import yaml diff --git a/nautobot_golden_config/filters.py b/nautobot_golden_config/filters.py index 45717400..6e785954 100644 --- a/nautobot_golden_config/filters.py +++ b/nautobot_golden_config/filters.py @@ -1,8 +1,7 @@ """Filters for UI and API Views.""" import django_filters - -from nautobot.core.filters import MultiValueDateTimeFilter, TreeNodeMultipleChoiceFilter, SearchFilter +from nautobot.core.filters import MultiValueDateTimeFilter, SearchFilter, TreeNodeMultipleChoiceFilter from nautobot.dcim.models import Device, DeviceType, Location, Manufacturer, Platform, Rack, RackGroup from nautobot.extras.filters import NaturalKeyOrPKMultipleChoiceFilter, NautobotFilterSet, StatusFilter from nautobot.extras.models import JobResult, Role, Status diff --git a/nautobot_golden_config/forms.py b/nautobot_golden_config/forms.py index f8e8ff19..0bc639ab 100644 --- a/nautobot_golden_config/forms.py +++ b/nautobot_golden_config/forms.py @@ -4,7 +4,6 @@ import json import django.forms as django_forms - from nautobot.apps import forms from nautobot.dcim.models import Device, DeviceType, Location, Manufacturer, Platform, Rack, RackGroup from nautobot.extras.forms import NautobotBulkEditForm, NautobotFilterForm, NautobotModelForm diff --git a/nautobot_golden_config/jobs.py b/nautobot_golden_config/jobs.py index 2a2335b7..34727cd4 100644 --- a/nautobot_golden_config/jobs.py +++ b/nautobot_golden_config/jobs.py @@ -5,6 +5,7 @@ # pylint: disable=arguments-differ from datetime import datetime + from django.utils.timezone import make_aware from nautobot.core.celery import register_jobs from nautobot.dcim.models import Device, DeviceType, Location, Manufacturer, Platform, Rack, RackGroup @@ -24,6 +25,7 @@ from nautobot_plugin_nornir.plugins.inventory.nautobot_orm import NautobotORMInventory from nornir.core.plugins.inventory import InventoryPluginRegister from nornir_nautobot.exceptions import NornirNautobotException + from nautobot_golden_config.choices import ConfigPlanTypeChoice from nautobot_golden_config.exceptions import BackupFailure, ComplianceFailure, IntendedGenerationFailure from nautobot_golden_config.models import ComplianceFeature, ConfigPlan, GoldenConfig diff --git a/nautobot_golden_config/metrics.py b/nautobot_golden_config/metrics.py index 80ced35f..f5b2815a 100644 --- a/nautobot_golden_config/metrics.py +++ b/nautobot_golden_config/metrics.py @@ -1,4 +1,5 @@ """Nautobot Golden Config app application level metrics .""" + from django.conf import settings from django.db.models import Count, F, Q from nautobot.dcim.models import Device diff --git a/nautobot_golden_config/migrations/0001_initial.py b/nautobot_golden_config/migrations/0001_initial.py index 2d926bf8..96a09c5f 100644 --- a/nautobot_golden_config/migrations/0001_initial.py +++ b/nautobot_golden_config/migrations/0001_initial.py @@ -1,10 +1,11 @@ # Generated by Django 3.1.8 on 2021-05-09 18:33 +import uuid + import django.core.serializers.json -from django.db import migrations, models import django.db.models.deletion import taggit.managers -import uuid +from django.db import migrations, models class Migration(migrations.Migration): diff --git a/nautobot_golden_config/migrations/0003_auto_20210510_2356.py b/nautobot_golden_config/migrations/0003_auto_20210510_2356.py index 1e9500c3..07336567 100644 --- a/nautobot_golden_config/migrations/0003_auto_20210510_2356.py +++ b/nautobot_golden_config/migrations/0003_auto_20210510_2356.py @@ -1,7 +1,7 @@ # Generated by Django 3.1.8 on 2021-05-10 23:56 -from django.db import migrations, models import django.db.models.deletion +from django.db import migrations, models class Migration(migrations.Migration): diff --git a/nautobot_golden_config/migrations/0004_auto_20210616_2234.py b/nautobot_golden_config/migrations/0004_auto_20210616_2234.py index 2d01e8bc..fc8f04f4 100644 --- a/nautobot_golden_config/migrations/0004_auto_20210616_2234.py +++ b/nautobot_golden_config/migrations/0004_auto_20210616_2234.py @@ -1,7 +1,7 @@ # Generated by Django 3.1.8 on 2021-06-16 22:34 -from django.db import migrations, models import django.db.models.deletion +from django.db import migrations, models class Migration(migrations.Migration): diff --git a/nautobot_golden_config/migrations/0005_json_compliance_rule.py b/nautobot_golden_config/migrations/0005_json_compliance_rule.py index 6ec713c7..8d388b58 100644 --- a/nautobot_golden_config/migrations/0005_json_compliance_rule.py +++ b/nautobot_golden_config/migrations/0005_json_compliance_rule.py @@ -1,6 +1,7 @@ -from django.db import migrations, models import json +from django.db import migrations, models + def jsonify(apps, schedma_editor): """Converts textfield to json in preparation for migration.""" diff --git a/nautobot_golden_config/migrations/0009_multiple_gc_settings_part_1.py b/nautobot_golden_config/migrations/0009_multiple_gc_settings_part_1.py index f3a47d2d..b728fefc 100644 --- a/nautobot_golden_config/migrations/0009_multiple_gc_settings_part_1.py +++ b/nautobot_golden_config/migrations/0009_multiple_gc_settings_part_1.py @@ -1,5 +1,5 @@ -from django.db import migrations, models import django.db.models.deletion +from django.db import migrations, models class Migration(migrations.Migration): diff --git a/nautobot_golden_config/migrations/0011_multiple_gc_settings_part_3.py b/nautobot_golden_config/migrations/0011_multiple_gc_settings_part_3.py index c30b2744..538859fd 100644 --- a/nautobot_golden_config/migrations/0011_multiple_gc_settings_part_3.py +++ b/nautobot_golden_config/migrations/0011_multiple_gc_settings_part_3.py @@ -1,5 +1,5 @@ -from django.db import migrations, models import django.db.models.deletion +from django.db import migrations, models class Migration(migrations.Migration): diff --git a/nautobot_golden_config/migrations/0019_convert_dynamicgroup_part_1.py b/nautobot_golden_config/migrations/0019_convert_dynamicgroup_part_1.py index 985f4a30..290d9f17 100644 --- a/nautobot_golden_config/migrations/0019_convert_dynamicgroup_part_1.py +++ b/nautobot_golden_config/migrations/0019_convert_dynamicgroup_part_1.py @@ -1,7 +1,7 @@ # Generated by Django 3.2.14 on 2022-07-11 14:18 -from django.db import migrations, models import django.db.models.deletion +from django.db import migrations, models class Migration(migrations.Migration): diff --git a/nautobot_golden_config/migrations/0020_convert_dynamicgroup_part_2.py b/nautobot_golden_config/migrations/0020_convert_dynamicgroup_part_2.py index 5de3e82b..14881220 100644 --- a/nautobot_golden_config/migrations/0020_convert_dynamicgroup_part_2.py +++ b/nautobot_golden_config/migrations/0020_convert_dynamicgroup_part_2.py @@ -1,7 +1,6 @@ # Generated by Django 3.2.14 on 2022-07-11 14:18 from django.db import migrations -from django.utils.text import slugify def create_dynamic_groups(apps, schedma_editor): diff --git a/nautobot_golden_config/migrations/0024_convert_custom_compliance_rules.py b/nautobot_golden_config/migrations/0024_convert_custom_compliance_rules.py index 9128b6bd..79d41ef2 100644 --- a/nautobot_golden_config/migrations/0024_convert_custom_compliance_rules.py +++ b/nautobot_golden_config/migrations/0024_convert_custom_compliance_rules.py @@ -1,4 +1,5 @@ from django.db import migrations + from nautobot_golden_config.choices import ComplianceRuleConfigTypeChoice diff --git a/nautobot_golden_config/migrations/0027_auto_20230915_1657.py b/nautobot_golden_config/migrations/0027_auto_20230915_1657.py index aa5308e4..0f22796d 100644 --- a/nautobot_golden_config/migrations/0027_auto_20230915_1657.py +++ b/nautobot_golden_config/migrations/0027_auto_20230915_1657.py @@ -1,7 +1,7 @@ # Generated by Django 3.2.20 on 2023-09-15 16:57 -from django.db import migrations, models import django.db.models.deletion +from django.db import migrations, models class Migration(migrations.Migration): diff --git a/nautobot_golden_config/migrations/0028_auto_20230916_1712_part2.py b/nautobot_golden_config/migrations/0028_auto_20230916_1712_part2.py index 033ce076..f943803d 100644 --- a/nautobot_golden_config/migrations/0028_auto_20230916_1712_part2.py +++ b/nautobot_golden_config/migrations/0028_auto_20230916_1712_part2.py @@ -1,8 +1,8 @@ # Generated by Django 3.2.20 on 2023-09-16 17:12 -from django.db import migrations, models import django.db.models.deletion import nautobot.core.models.fields +from django.db import migrations, models class Migration(migrations.Migration): diff --git a/nautobot_golden_config/migrations/0030_alter_goldenconfig_device.py b/nautobot_golden_config/migrations/0030_alter_goldenconfig_device.py index 4637bc91..823076c7 100644 --- a/nautobot_golden_config/migrations/0030_alter_goldenconfig_device.py +++ b/nautobot_golden_config/migrations/0030_alter_goldenconfig_device.py @@ -1,7 +1,7 @@ # Generated by Django 3.2.21 on 2023-09-25 12:36 -from django.db import migrations, models import django.db.models.deletion +from django.db import migrations, models class Migration(migrations.Migration): diff --git a/nautobot_golden_config/models.py b/nautobot_golden_config/models.py index 924f5158..3e31debd 100644 --- a/nautobot_golden_config/models.py +++ b/nautobot_golden_config/models.py @@ -15,8 +15,7 @@ from nautobot.extras.models.statuses import StatusField from nautobot.extras.utils import extras_features from netutils.config.compliance import feature_compliance -from xmldiff import main, actions - +from xmldiff import actions, main from nautobot_golden_config.choices import ComplianceRuleConfigTypeChoice, ConfigPlanTypeChoice, RemediationTypeChoice from nautobot_golden_config.utilities.constant import ENABLE_SOTAGG, PLUGIN_CFG @@ -348,9 +347,7 @@ class ConfigCompliance(PrimaryModel): # pylint: disable=too-many-ancestors # Used for django-pivot, both compliance and compliance_int should be set. compliance_int = models.IntegerField(blank=True) - def to_objectchange( - self, action, *, related_object=None, object_data_extra=None, object_data_exclude=None - ): # pylint: disable=arguments-differ + def to_objectchange(self, action, *, related_object=None, object_data_extra=None, object_data_exclude=None): # pylint: disable=arguments-differ """Remove actual and intended configuration from changelog.""" fields_to_exclude = ["actual", "intended"] if not object_data_exclude: @@ -451,9 +448,7 @@ class GoldenConfig(PrimaryModel): # pylint: disable=too-many-ancestors 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 - ): # pylint: disable=arguments-differ + def to_objectchange(self, action, *, related_object=None, object_data_extra=None, object_data_exclude=None): # pylint: disable=arguments-differ """Remove actual and intended configuration from changelog.""" fields_to_exclude = ["backup_config", "intended_config", "compliance_config"] if not object_data_exclude: diff --git a/nautobot_golden_config/nornir_plays/config_backup.py b/nautobot_golden_config/nornir_plays/config_backup.py index 952d1424..01390428 100644 --- a/nautobot_golden_config/nornir_plays/config_backup.py +++ b/nautobot_golden_config/nornir_plays/config_backup.py @@ -13,6 +13,7 @@ from nornir.core.task import Result, Task from nornir_nautobot.exceptions import NornirNautobotException from nornir_nautobot.plugins.tasks.dispatcher import dispatcher + from nautobot_golden_config.exceptions import BackupFailure from nautobot_golden_config.models import ConfigRemove, ConfigReplace, GoldenConfig from nautobot_golden_config.nornir_plays.processor import ProcessGoldenConfig diff --git a/nautobot_golden_config/nornir_plays/config_compliance.py b/nautobot_golden_config/nornir_plays/config_compliance.py index 4f44bf76..5a2531ef 100644 --- a/nautobot_golden_config/nornir_plays/config_compliance.py +++ b/nautobot_golden_config/nornir_plays/config_compliance.py @@ -6,9 +6,9 @@ import os from collections import defaultdict from datetime import datetime -from django.utils.timezone import make_aware -from lxml import etree # nosec +from django.utils.timezone import make_aware +from lxml import etree from nautobot_plugin_nornir.constants import NORNIR_SETTINGS from nautobot_plugin_nornir.plugins.inventory.nautobot_orm import NautobotORMInventory from netutils.config.compliance import _open_file_config, parser_map, section_config @@ -16,19 +16,20 @@ from nornir.core.plugins.inventory import InventoryPluginRegister from nornir.core.task import Result, Task from nornir_nautobot.exceptions import NornirNautobotException + from nautobot_golden_config.choices import ComplianceRuleConfigTypeChoice from nautobot_golden_config.exceptions import ComplianceFailure from nautobot_golden_config.models import ComplianceRule, ConfigCompliance, GoldenConfig from nautobot_golden_config.nornir_plays.processor import ProcessGoldenConfig from nautobot_golden_config.utilities.db_management import close_threaded_db_connections -from nautobot_golden_config.utilities.logger import NornirLogger from nautobot_golden_config.utilities.helper import ( get_json_config, get_xml_config, + get_xml_subtree_with_full_path, render_jinja_template, verify_settings, - get_xml_subtree_with_full_path, ) +from nautobot_golden_config.utilities.logger import NornirLogger InventoryPluginRegister.register("nautobot-inventory", NautobotORMInventory) LOGGER = logging.getLogger(__name__) diff --git a/nautobot_golden_config/nornir_plays/config_deployment.py b/nautobot_golden_config/nornir_plays/config_deployment.py index 2f5480ca..d0eac642 100644 --- a/nautobot_golden_config/nornir_plays/config_deployment.py +++ b/nautobot_golden_config/nornir_plays/config_deployment.py @@ -1,29 +1,25 @@ """Nornir job for deploying configurations.""" -from datetime import datetime import logging +from datetime import datetime from django.utils.timezone import make_aware - from nautobot.dcim.models import Device from nautobot.extras.models import Status - +from nautobot_plugin_nornir.constants import NORNIR_SETTINGS +from nautobot_plugin_nornir.plugins.inventory.nautobot_orm import NautobotORMInventory from nornir import InitNornir from nornir.core.exceptions import NornirSubTaskError from nornir.core.plugins.inventory import InventoryPluginRegister from nornir.core.task import Result, Task - from nornir_nautobot.exceptions import NornirNautobotException from nornir_nautobot.plugins.tasks.dispatcher import dispatcher -from nautobot_plugin_nornir.constants import NORNIR_SETTINGS -from nautobot_plugin_nornir.plugins.inventory.nautobot_orm import NautobotORMInventory - from nautobot_golden_config.nornir_plays.processor import ProcessGoldenConfig +from nautobot_golden_config.utilities.constant import DEFAULT_DEPLOY_STATUS +from nautobot_golden_config.utilities.db_management import close_threaded_db_connections from nautobot_golden_config.utilities.helper import dispatch_params from nautobot_golden_config.utilities.logger import NornirLogger -from nautobot_golden_config.utilities.db_management import close_threaded_db_connections -from nautobot_golden_config.utilities.constant import DEFAULT_DEPLOY_STATUS InventoryPluginRegister.register("nautobot-inventory", NautobotORMInventory) @@ -60,10 +56,9 @@ def run_deployment(task: Task, logger: logging.Logger, config_plan_qs, deploy_jo if not task_changed and not task_failed: plans_to_deploy.update(status=Status.objects.get(name="Completed")) logger.info("Nothing was deployed to the device.", extra={"object": obj}) - else: - if not task_failed: - logger.info("Successfully deployed configuration to device.", extra={"object": obj}) - plans_to_deploy.update(status=Status.objects.get(name="Completed")) + elif not task_failed: + logger.info("Successfully deployed configuration to device.", extra={"object": obj}) + plans_to_deploy.update(status=Status.objects.get(name="Completed")) except NornirSubTaskError as error: task_result = None plans_to_deploy.update(status=Status.objects.get(name="Failed")) diff --git a/nautobot_golden_config/nornir_plays/config_intended.py b/nautobot_golden_config/nornir_plays/config_intended.py index 60c89d18..dc97f3a3 100644 --- a/nautobot_golden_config/nornir_plays/config_intended.py +++ b/nautobot_golden_config/nornir_plays/config_intended.py @@ -6,13 +6,14 @@ from datetime import datetime from django.utils.timezone import make_aware +from nautobot_plugin_nornir.constants import NORNIR_SETTINGS +from nautobot_plugin_nornir.plugins.inventory.nautobot_orm import NautobotORMInventory from nornir import InitNornir from nornir.core.plugins.inventory import InventoryPluginRegister from nornir.core.task import Result, Task from nornir_nautobot.exceptions import NornirNautobotException from nornir_nautobot.plugins.tasks.dispatcher import dispatcher -from nautobot_plugin_nornir.constants import NORNIR_SETTINGS -from nautobot_plugin_nornir.plugins.inventory.nautobot_orm import NautobotORMInventory + from nautobot_golden_config.exceptions import IntendedGenerationFailure from nautobot_golden_config.models import GoldenConfig from nautobot_golden_config.nornir_plays.processor import ProcessGoldenConfig @@ -26,7 +27,6 @@ ) from nautobot_golden_config.utilities.logger import NornirLogger - InventoryPluginRegister.register("nautobot-inventory", NautobotORMInventory) LOGGER = logging.getLogger(__name__) @@ -64,7 +64,7 @@ def run_template( # pylint: disable=too-many-arguments,too-many-locals jinja_template = render_jinja_template(obj, logger, settings.jinja_path_template) job_class_instance.request.user = job_class_instance.user status, device_data = graph_ql_query(job_class_instance.request, obj, settings.sot_agg_query.query) - if status != 200: + if status != 200: # noqa: PLR2004 error_msg = f"`E3012:` The GraphQL query return a status of {str(status)} with error of {str(device_data)}" logger.error(error_msg, extra={"object": obj}) raise NornirNautobotException(error_msg) diff --git a/nautobot_golden_config/nornir_plays/processor.py b/nautobot_golden_config/nornir_plays/processor.py index 4043dcaa..956e3560 100644 --- a/nautobot_golden_config/nornir_plays/processor.py +++ b/nautobot_golden_config/nornir_plays/processor.py @@ -1,4 +1,5 @@ """Processor used by Golden Config to catch unknown errors.""" + from nornir.core.inventory import Host from nornir.core.task import MultiResult, Result, Task from nornir_nautobot.exceptions import NornirNautobotException diff --git a/nautobot_golden_config/signals.py b/nautobot_golden_config/signals.py index 1706bddb..d5966c04 100755 --- a/nautobot_golden_config/signals.py +++ b/nautobot_golden_config/signals.py @@ -1,10 +1,11 @@ """Signal helpers.""" + from django.apps import apps as global_apps from django.db.models.signals import post_save from django.dispatch import receiver +from nautobot.core.choices import ColorChoices from nautobot.dcim.models import Platform -from nautobot.core.choices import ColorChoices from nautobot_golden_config import models diff --git a/nautobot_golden_config/tables.py b/nautobot_golden_config/tables.py index 9031c6d1..c4588815 100644 --- a/nautobot_golden_config/tables.py +++ b/nautobot_golden_config/tables.py @@ -1,11 +1,12 @@ """Django Tables2 classes for golden_config app.""" + import copy from django.utils.html import format_html from django_tables2 import Column, LinkColumn, TemplateColumn from django_tables2.utils import A -from nautobot.extras.tables import StatusTableMixin from nautobot.apps.tables import BaseTable, BooleanColumn, TagColumn, ToggleColumn +from nautobot.extras.tables import StatusTableMixin from nautobot_golden_config import models from nautobot_golden_config.utilities.constant import CONFIG_FEATURES, ENABLE_BACKUP, ENABLE_COMPLIANCE, ENABLE_INTENDED diff --git a/nautobot_golden_config/template_content.py b/nautobot_golden_config/template_content.py index ff97ce05..fb92b0fd 100644 --- a/nautobot_golden_config/template_content.py +++ b/nautobot_golden_config/template_content.py @@ -1,8 +1,10 @@ """Added content to the device model view for config compliance.""" + from django.core.exceptions import ObjectDoesNotExist from django.db.models import Count, Q from django.urls import reverse from nautobot.extras.plugins import PluginTemplateExtension + from nautobot_golden_config.models import ConfigCompliance, GoldenConfig from nautobot_golden_config.utilities.constant import CONFIG_FEATURES, ENABLE_COMPLIANCE diff --git a/nautobot_golden_config/templatetags/json_helpers.py b/nautobot_golden_config/templatetags/json_helpers.py index 5b3df9c3..9b2dd7d5 100644 --- a/nautobot_golden_config/templatetags/json_helpers.py +++ b/nautobot_golden_config/templatetags/json_helpers.py @@ -1,4 +1,5 @@ """Helper for JSON rendering that extends what Nautobot Core provides.""" + import json from django import template diff --git a/nautobot_golden_config/tests/conftest.py b/nautobot_golden_config/tests/conftest.py index ec552b24..97eefadb 100644 --- a/nautobot_golden_config/tests/conftest.py +++ b/nautobot_golden_config/tests/conftest.py @@ -1,18 +1,18 @@ """Params for testing.""" -from datetime import datetime + +from datetime import datetime, timezone + from django.contrib.auth import get_user_model from django.contrib.contenttypes.models import ContentType from django.utils.text import slugify - from nautobot.dcim.models import Device, DeviceType, Location, LocationType, Manufacturer, Platform, Rack, RackGroup from nautobot.extras.choices import JobResultStatusChoices from nautobot.extras.datasources.registry import get_datasource_contents -from nautobot.extras.models import GitRepository, GraphQLQuery, JobResult, Role, Status, Tag, DynamicGroup +from nautobot.extras.models import DynamicGroup, GitRepository, GraphQLQuery, JobResult, Role, Status, Tag from nautobot.tenancy.models import Tenant, TenantGroup -import pytz -from nautobot_golden_config.models import GoldenConfigSetting + from nautobot_golden_config.choices import ComplianceRuleConfigTypeChoice -from nautobot_golden_config.models import ComplianceFeature, ComplianceRule, ConfigCompliance +from nautobot_golden_config.models import ComplianceFeature, ComplianceRule, ConfigCompliance, GoldenConfigSetting User = get_user_model() @@ -545,7 +545,7 @@ def create_job_result() -> None: user=user, ) result.status = JobResultStatusChoices.STATUS_SUCCESS - result.completed = datetime.now(pytz.UTC) + result.completed = datetime.now(timezone.utc) result.validated_save() return result diff --git a/nautobot_golden_config/tests/forms/test_golden_config_settings.py b/nautobot_golden_config/tests/forms/test_golden_config_settings.py index a2da7695..6a583975 100644 --- a/nautobot_golden_config/tests/forms/test_golden_config_settings.py +++ b/nautobot_golden_config/tests/forms/test_golden_config_settings.py @@ -1,12 +1,13 @@ """Tests for Golden Configuration Settings Form.""" + from unittest import mock from django.test import TestCase +from nautobot.extras.models import DynamicGroup, GitRepository -from nautobot.extras.models import GitRepository, DynamicGroup from nautobot_golden_config.forms import GoldenConfigSettingForm from nautobot_golden_config.models import GoldenConfigSetting -from nautobot_golden_config.tests.conftest import create_git_repos, create_device_data +from nautobot_golden_config.tests.conftest import create_device_data, create_git_repos class GoldenConfigSettingFormTest(TestCase): @@ -33,7 +34,7 @@ def test_no_query_no_scope_success(self): "intended_repository": GitRepository.objects.get(name="test-intended-repo-1"), "intended_path_template": "{{ obj.location.name }}/{{ obj.name }}.cfg", "backup_test_connectivity": True, - "dynamic_group": DynamicGroup.objects.first() + "dynamic_group": DynamicGroup.objects.first(), } ) self.assertTrue(form.is_valid()) @@ -53,7 +54,7 @@ def test_no_query_fail(self): "intended_repository": GitRepository.objects.get(name="test-intended-repo-1"), "intended_path_template": "{{ obj.location.name }}/{{ obj.name }}.cfg", "backup_test_connectivity": True, - "dynamic_group": DynamicGroup.objects.first() + "dynamic_group": DynamicGroup.objects.first(), } ) self.assertFalse(form.is_valid()) diff --git a/nautobot_golden_config/tests/test_api.py b/nautobot_golden_config/tests/test_api.py index 08104fad..1e7e6b7d 100644 --- a/nautobot_golden_config/tests/test_api.py +++ b/nautobot_golden_config/tests/test_api.py @@ -1,15 +1,14 @@ """Unit tests for nautobot_golden_config.""" + from copy import deepcopy from django.contrib.auth import get_user_model from django.contrib.contenttypes.models import ContentType from django.urls import reverse - -from rest_framework import status - +from nautobot.core.testing import APITestCase, APIViewTestCases from nautobot.dcim.models import Device, Platform from nautobot.extras.models import DynamicGroup, GitRepository, GraphQLQuery, Status -from nautobot.core.testing import APITestCase, APIViewTestCases +from rest_framework import status from nautobot_golden_config.choices import RemediationTypeChoice from nautobot_golden_config.models import ConfigPlan, GoldenConfigSetting, RemediationSetting diff --git a/nautobot_golden_config/tests/test_basic.py b/nautobot_golden_config/tests/test_basic.py index 260e90e8..d72f2d02 100644 --- a/nautobot_golden_config/tests/test_basic.py +++ b/nautobot_golden_config/tests/test_basic.py @@ -1,6 +1,8 @@ """Basic tests that do not require Django.""" + import os import unittest + import toml @@ -15,8 +17,9 @@ def test_version(self): with open(f"{parent_path}/docs/requirements.txt", "r", encoding="utf-8") as file: requirements = [line for line in file.read().splitlines() if (len(line) > 0 and not line.startswith("#"))] for pkg in requirements: - if len(pkg.split("==")) == 2: - pkg, version = pkg.split("==") + package_name = pkg + if len(pkg.split("==")) == 2: # noqa: PLR2004 + package_name, version = pkg.split("==") else: version = "*" - self.assertEqual(poetry_details[pkg], version) + self.assertEqual(poetry_details[package_name], version) diff --git a/nautobot_golden_config/tests/test_datasources.py b/nautobot_golden_config/tests/test_datasources.py index 26e0bbc2..135ff505 100644 --- a/nautobot_golden_config/tests/test_datasources.py +++ b/nautobot_golden_config/tests/test_datasources.py @@ -1,11 +1,12 @@ """Unit tests for nautobot_golden_config datasources.""" + from unittest import skip from unittest.mock import Mock from django.test import TestCase from nautobot.dcim.models import Platform -from nautobot_golden_config.datasources import get_id_kwargs, MissingReference +from nautobot_golden_config.datasources import MissingReference, get_id_kwargs from nautobot_golden_config.models import ComplianceFeature diff --git a/nautobot_golden_config/tests/test_filters.py b/nautobot_golden_config/tests/test_filters.py index c9eb46d4..41f506d5 100644 --- a/nautobot_golden_config/tests/test_filters.py +++ b/nautobot_golden_config/tests/test_filters.py @@ -2,9 +2,10 @@ from django.contrib.contenttypes.models import ContentType from django.test import TestCase +from nautobot.core.testing import FilterTestCases from nautobot.dcim.models import Device, Platform from nautobot.extras.models import Status, Tag -from nautobot.core.testing import FilterTestCases + from nautobot_golden_config import filters, models from .conftest import create_device_data, create_feature_rule_cli, create_feature_rule_json, create_job_result @@ -390,72 +391,73 @@ class ConfigPlanFilterTestCase(FilterTestCases.FilterTestCase): queryset = models.ConfigPlan.objects.all() filterset = filters.ConfigPlanFilterSet - def setUp(self): + @classmethod + def setUpTestData(cls): """Setup Object.""" create_device_data() - self.device1 = Device.objects.get(name="Device 1") - self.device2 = Device.objects.get(name="Device 2") - self.rule1 = create_feature_rule_cli(self.device1, feature="Feature 1") - self.feature1 = self.rule1.feature - self.rule2 = create_feature_rule_cli(self.device2, feature="Feature 2") - self.feature2 = self.rule2.feature - self.rule3 = create_feature_rule_cli(self.device1, feature="Feature 3") - self.feature3 = self.rule3.feature - self.status1 = Status.objects.get(name="Not Approved") - self.status2 = Status.objects.get(name="Approved") - self.tag1, _ = Tag.objects.get_or_create(name="Tag 1") - self.tag2, _ = Tag.objects.get_or_create(name="Tag 2") - self.tag1.content_types.set([ContentType.objects.get_for_model(models.ConfigPlan)]) - self.tag2.content_types.set([ContentType.objects.get_for_model(models.ConfigPlan)]) - self.job_result1 = create_job_result() - self.job_result2 = create_job_result() - self.config_plan1 = models.ConfigPlan.objects.create( - device=self.device1, + cls.device1 = Device.objects.get(name="Device 1") + cls.device2 = Device.objects.get(name="Device 2") + cls.rule1 = create_feature_rule_cli(cls.device1, feature="Feature 1") + cls.feature1 = cls.rule1.feature + cls.rule2 = create_feature_rule_cli(cls.device2, feature="Feature 2") + cls.feature2 = cls.rule2.feature + cls.rule3 = create_feature_rule_cli(cls.device1, feature="Feature 3") + cls.feature3 = cls.rule3.feature + cls.status1 = Status.objects.get(name="Not Approved") + cls.status2 = Status.objects.get(name="Approved") + cls.tag1, _ = Tag.objects.get_or_create(name="Tag 1") + cls.tag2, _ = Tag.objects.get_or_create(name="Tag 2") + cls.tag1.content_types.set([ContentType.objects.get_for_model(models.ConfigPlan)]) + cls.tag2.content_types.set([ContentType.objects.get_for_model(models.ConfigPlan)]) + cls.job_result1 = create_job_result() + cls.job_result2 = create_job_result() + cls.config_plan1 = models.ConfigPlan.objects.create( + device=cls.device1, plan_type="intended", created="2020-01-01", config_set="intended test", change_control_id="12345", - status=self.status2, - plan_result_id=self.job_result1.id, + status=cls.status2, + plan_result_id=cls.job_result1.id, ) - self.config_plan1.tags.add(self.tag1) - self.config_plan1.feature.add(self.feature1) - self.config_plan1.validated_save() - self.config_plan2 = models.ConfigPlan.objects.create( - device=self.device1, + cls.config_plan1.tags.add(cls.tag1) + cls.config_plan1.feature.add(cls.feature1) + cls.config_plan1.validated_save() + cls.config_plan2 = models.ConfigPlan.objects.create( + device=cls.device1, plan_type="missing", created="2020-01-02", config_set="missing test", change_control_id="23456", - status=self.status1, - plan_result_id=self.job_result1.id, + status=cls.status1, + plan_result_id=cls.job_result1.id, ) - self.config_plan2.tags.add(self.tag2) - self.config_plan2.feature.add(self.feature2) - self.config_plan2.validated_save() - self.config_plan3 = models.ConfigPlan.objects.create( - device=self.device2, + cls.config_plan2.tags.add(cls.tag2) + cls.config_plan2.feature.add(cls.feature2) + cls.config_plan2.validated_save() + cls.config_plan3 = models.ConfigPlan.objects.create( + device=cls.device2, plan_type="remediation", created="2020-01-03", config_set="remediation test", change_control_id="34567", - status=self.status2, - plan_result_id=self.job_result2.id, + status=cls.status2, + plan_result_id=cls.job_result2.id, ) - self.config_plan3.tags.add(self.tag2) - self.config_plan3.feature.set([self.feature1, self.feature3]) - self.config_plan3.validated_save() - self.config_plan4 = models.ConfigPlan.objects.create( - device=self.device2, + cls.config_plan3.tags.add(cls.tag2) + cls.config_plan3.feature.set([cls.feature1, cls.feature3]) + cls.config_plan3.validated_save() + cls.config_plan4 = models.ConfigPlan.objects.create( + device=cls.device2, plan_type="manual", created="2020-01-04", config_set="manual test", change_control_id="45678", - status=self.status1, - plan_result_id=self.job_result1.id, + status=cls.status1, + plan_result_id=cls.job_result1.id, ) - self.config_plan4.tags.add(self.tag1) - self.config_plan4.validated_save() + cls.config_plan4.tags.add(cls.tag1) + cls.config_plan4.validated_save() def test_full(self): """Test without filtering to ensure all have been added.""" diff --git a/nautobot_golden_config/tests/test_graphql.py b/nautobot_golden_config/tests/test_graphql.py index 386f0756..8c9e6960 100644 --- a/nautobot_golden_config/tests/test_graphql.py +++ b/nautobot_golden_config/tests/test_graphql.py @@ -6,21 +6,19 @@ from django.contrib.contenttypes.models import ContentType from django.test import TestCase from django.test.client import RequestFactory - -from graphql import get_default_backend from graphene_django.settings import graphene_settings - -from nautobot.dcim.models import Platform, LocationType, Location, Device, Manufacturer, DeviceType -from nautobot.extras.models import GitRepository, GraphQLQuery, DynamicGroup, Role, Status +from graphql import get_default_backend +from nautobot.dcim.models import Device, DeviceType, Location, LocationType, Manufacturer, Platform +from nautobot.extras.models import DynamicGroup, GitRepository, GraphQLQuery, Role, Status from nautobot_golden_config.models import ( ComplianceFeature, ComplianceRule, ConfigCompliance, - GoldenConfig, - GoldenConfigSetting, ConfigRemove, ConfigReplace, + GoldenConfig, + GoldenConfigSetting, ) from .conftest import create_saved_queries diff --git a/nautobot_golden_config/tests/test_helpers.py b/nautobot_golden_config/tests/test_helpers.py index 917299f7..2bec5a92 100644 --- a/nautobot_golden_config/tests/test_helpers.py +++ b/nautobot_golden_config/tests/test_helpers.py @@ -1,27 +1,26 @@ """Unit tests for nautobot_golden_config helpers.""" + import os from unittest import mock -import jinja2 +import jinja2 from django.contrib.auth import get_user_model -from django.test import TestCase from django.contrib.contenttypes.models import ContentType - -from nautobot.extras.models import SecretsGroup, Secret, SecretsGroupAssociation +from django.test import TestCase from nautobot.extras.choices import SecretsGroupAccessTypeChoices, SecretsGroupSecretTypeChoices +from nautobot.extras.models import Secret, SecretsGroup, SecretsGroupAssociation from nautobot.users.models import ObjectPermission +from nautobot_golden_config.models import GoldenConfig from nautobot_golden_config.utilities.config_postprocessing import ( + get_config_postprocessing, get_secret_by_secret_group_name, render_secrets, - get_config_postprocessing, ) -from nautobot_golden_config.models import GoldenConfig from nautobot_golden_config.utilities.constant import PLUGIN_CFG from .conftest import create_device - # Use the proper swappable User model User = get_user_model() diff --git a/nautobot_golden_config/tests/test_jobs.py b/nautobot_golden_config/tests/test_jobs.py index fcf099a5..2debfd8b 100755 --- a/nautobot_golden_config/tests/test_jobs.py +++ b/nautobot_golden_config/tests/test_jobs.py @@ -1,15 +1,18 @@ """Basic Job Test.""" -from unittest.mock import patch, MagicMock + +from unittest.mock import MagicMock, patch + from nautobot.apps.testing import TransactionTestCase, create_job_result_and_run_job -from nautobot.extras.models import JobLogEntry from nautobot.dcim.models import Device +from nautobot.extras.models import JobLogEntry + +from nautobot_golden_config import jobs from nautobot_golden_config.tests.conftest import ( create_device, create_orphan_device, dgs_gc_settings_and_job_repo_objects, ) from nautobot_golden_config.utilities import constant -from nautobot_golden_config import jobs @patch("nautobot_golden_config.nornir_plays.config_backup.run_backup", MagicMock(return_value="foo")) diff --git a/nautobot_golden_config/tests/test_nornir_plays/test_config_compliance.py b/nautobot_golden_config/tests/test_nornir_plays/test_config_compliance.py index d6125395..36c05a15 100644 --- a/nautobot_golden_config/tests/test_nornir_plays/test_config_compliance.py +++ b/nautobot_golden_config/tests/test_nornir_plays/test_config_compliance.py @@ -1,10 +1,11 @@ """Unit tests for nautobot_golden_config nornir compliance.""" -import unittest import json -from unittest.mock import patch, Mock, MagicMock +import unittest +from unittest.mock import MagicMock, Mock, patch + from nautobot_golden_config.choices import ComplianceRuleConfigTypeChoice -from nautobot_golden_config.nornir_plays.config_compliance import get_rules, get_config_element +from nautobot_golden_config.nornir_plays.config_compliance import get_config_element, get_rules class ConfigComplianceTest(unittest.TestCase): diff --git a/nautobot_golden_config/tests/test_utilities/test_config_plan.py b/nautobot_golden_config/tests/test_utilities/test_config_plan.py index 5ed1bf40..b83fb5b2 100644 --- a/nautobot_golden_config/tests/test_utilities/test_config_plan.py +++ b/nautobot_golden_config/tests/test_utilities/test_config_plan.py @@ -1,13 +1,14 @@ """Unit tests for the nautobot_golden_config utilities config_plan.""" + import unittest from unittest.mock import Mock, patch +from nautobot_golden_config.tests.conftest import create_config_compliance, create_device, create_feature_rule_cli from nautobot_golden_config.utilities.config_plan import ( + config_plan_default_status, generate_config_set_from_compliance_feature, generate_config_set_from_manual, - config_plan_default_status, ) -from nautobot_golden_config.tests.conftest import create_device, create_feature_rule_cli, create_config_compliance class ConfigPlanTest(unittest.TestCase): diff --git a/nautobot_golden_config/tests/test_utilities/test_git.py b/nautobot_golden_config/tests/test_utilities/test_git.py index cae1f760..4188fe30 100644 --- a/nautobot_golden_config/tests/test_utilities/test_git.py +++ b/nautobot_golden_config/tests/test_utilities/test_git.py @@ -27,7 +27,7 @@ def mock_get_secret_value( # pylint: disable=unused-argument,inconsistent-retur mock_obj.filesystem_path = "/fake/path" mock_obj.remote_url = "https://fake.git/org/repository.git" - mock_obj._token = "fake token" # nosec pylint: disable=protected-access + mock_obj._token = "fake token" # pylint: disable=protected-access mock_obj.username = None mock_obj.secrets_group = Mock(get_secret_value=mock_get_secret_value) self.mock_obj = mock_obj diff --git a/nautobot_golden_config/tests/test_utilities/test_graphql.py b/nautobot_golden_config/tests/test_utilities/test_graphql.py index 0728064b..3dc55372 100644 --- a/nautobot_golden_config/tests/test_utilities/test_graphql.py +++ b/nautobot_golden_config/tests/test_utilities/test_graphql.py @@ -1,10 +1,11 @@ """Unit tests for nautobot_golden_config utilities graphql.""" -from unittest.mock import patch from unittest import skip +from unittest.mock import patch from nautobot.core.testing import TestCase from nautobot.dcim.models import Device + from nautobot_golden_config.utilities.graphql import graph_ql_query diff --git a/nautobot_golden_config/tests/test_utilities/test_helpers.py b/nautobot_golden_config/tests/test_utilities/test_helpers.py index b39776da..b0903240 100644 --- a/nautobot_golden_config/tests/test_utilities/test_helpers.py +++ b/nautobot_golden_config/tests/test_utilities/test_helpers.py @@ -4,12 +4,13 @@ from unittest.mock import MagicMock, patch from django.contrib.contenttypes.models import ContentType -from django.test import TestCase from django.template import engines +from django.test import TestCase from jinja2 import exceptions as jinja_errors -from nautobot.dcim.models import Device, Platform, Location, LocationType +from nautobot.dcim.models import Device, Location, LocationType, Platform from nautobot.extras.models import DynamicGroup, GitRepository, GraphQLQuery, Status, Tag from nornir_nautobot.exceptions import NornirNautobotException + from nautobot_golden_config.models import GoldenConfigSetting from nautobot_golden_config.tests.conftest import create_device, create_helper_repo, create_orphan_device from nautobot_golden_config.utilities.helper import ( diff --git a/nautobot_golden_config/tests/test_views.py b/nautobot_golden_config/tests/test_views.py index cf49e3f4..51eca381 100644 --- a/nautobot_golden_config/tests/test_views.py +++ b/nautobot_golden_config/tests/test_views.py @@ -1,19 +1,18 @@ """Unit tests for nautobot_golden_config views.""" -from unittest import mock, skip import datetime +from unittest import mock, skip -from lxml import html - +import nautobot from django.contrib.auth import get_user_model from django.contrib.contenttypes.models import ContentType -from django.test import override_settings, RequestFactory, TestCase +from django.test import RequestFactory, override_settings from django.urls import reverse - +from lxml import html from nautobot.core.models.querysets import RestrictedQuerySet +from nautobot.core.testing import TestCase, ViewTestCases from nautobot.dcim.models import Device from nautobot.extras.models import Relationship, RelationshipAssociation, Status -from nautobot.core.testing import ViewTestCases from nautobot_golden_config import models, views @@ -22,10 +21,12 @@ User = get_user_model() +@override_settings(EXEMPT_VIEW_PERMISSIONS=["*"]) class ConfigComplianceOverviewHelperTestCase(TestCase): """Test ConfigComplianceOverviewHelper.""" - def setUp(self): + @classmethod + def setUpTestData(cls): """Set up base objects.""" create_device_data() dev01 = Device.objects.get(name="Device 1") @@ -52,9 +53,7 @@ def setUp(self): ) # TODO: 2.0 turn this back on. - # self.ccoh = views.ConfigComplianceOverviewOverviewHelper - User.objects.create_superuser(username="views", password="incredible") - self.client.login(username="views", password="incredible") + # cls.ccoh = views.ConfigComplianceOverviewOverviewHelper def test_plot_visual_no_devices(self): # TODO: 2.0 turn this back on. @@ -103,56 +102,44 @@ def test_config_compliance_details_sotagg_no_error( mock_graph_ql_query.assert_called() +@override_settings(EXEMPT_VIEW_PERMISSIONS=["*"]) class ConfigReplaceListViewTestCase(TestCase): """Test ConfigReplaceListView.""" - def setUp(self): + _csv_headers = "name,platform,description,regex,replace" + _entry_name = "test name" + _entry_description = "test description" + _entry_regex = "^startswiththeend$" + _entry_replace = "" + + @classmethod + def setUpTestData(cls): """Set up base objects.""" create_device_data() - User.objects.create_superuser(username="views", password="incredible") - self.client.login(username="views", password="incredible") - self._delete_test_entry() + cls._delete_test_entry() models.ConfigReplace.objects.create( - name=self._entry_name, + name=cls._entry_name, platform=Device.objects.first().platform, - description=self._entry_description, - regex=self._entry_regex, - replace=self._entry_replace, + description=cls._entry_description, + regex=cls._entry_regex, + replace=cls._entry_replace, ) @property def _url(self): return reverse("plugins:nautobot_golden_config:configreplace_list") - def _delete_test_entry(self): + @classmethod + def _delete_test_entry(cls): try: - entry = models.ConfigReplace.objects.get(name=self._entry_name) + entry = models.ConfigReplace.objects.get(name=cls._entry_name) entry.delete() except models.ConfigReplace.DoesNotExist: pass - @property - def _csv_headers(self): - return "name,platform,description,regex,replace" - - @property - def _entry_name(self): - return "test name" - - @property - def _entry_description(self): - return "test description" - - @property - def _entry_regex(self): - return "^startswiththeend$" - - @property - def _entry_replace(self): - return "" - def test_configreplace_import(self): self._delete_test_entry() + self.add_permissions("nautobot_golden_config.add_configreplace") platform = Device.objects.first().platform import_entry = ( f"{self._entry_name},{platform.id},{self._entry_description},{self._entry_regex},{self._entry_replace}" @@ -171,15 +158,16 @@ def test_configreplace_import(self): class GoldenConfigListViewTestCase(TestCase): """Test GoldenConfigListView.""" - def setUp(self): + user_permissions = ["nautobot_golden_config.view_goldenconfig", "nautobot_golden_config.change_goldenconfig"] + + @classmethod + def setUpTestData(cls): """Set up base objects.""" create_device_data() - User.objects.create_superuser(username="views", password="incredible") - self.client.login(username="views", password="incredible") - self.gc_settings = models.GoldenConfigSetting.objects.first() - self.gc_dynamic_group = self.gc_settings.dynamic_group - self.gc_dynamic_group.filter = {"name": [dev.name for dev in Device.objects.all()]} - self.gc_dynamic_group.validated_save() + cls.gc_settings = models.GoldenConfigSetting.objects.first() + cls.gc_dynamic_group = cls.gc_settings.dynamic_group + cls.gc_dynamic_group.filter = {"name": [dev.name for dev in Device.objects.all()]} + cls.gc_dynamic_group.validated_save() models.GoldenConfig.objects.create(device=Device.objects.first()) def _get_golden_config_table_header(self): @@ -190,6 +178,8 @@ def _get_golden_config_table_header(self): @property def _text_table_headers(self): + if nautobot.__version__ >= "2.3.0": + return ["Device", "Backup Status", "Intended Status", "Compliance Status", "Dynamic Groups", "Actions"] return ["Device", "Backup Status", "Intended Status", "Compliance Status", "Actions"] @property diff --git a/nautobot_golden_config/urls.py b/nautobot_golden_config/urls.py index ee2043d3..f6b55835 100644 --- a/nautobot_golden_config/urls.py +++ b/nautobot_golden_config/urls.py @@ -1,9 +1,10 @@ """Django urlpatterns declaration for config compliance app.""" -from django.urls import path + from django.templatetags.static import static +from django.urls import path from django.views.generic import RedirectView - from nautobot.core.views.routers import NautobotUIViewSetRouter + from nautobot_golden_config import views app_name = "nautobot_golden_config" diff --git a/nautobot_golden_config/utilities/config_plan.py b/nautobot_golden_config/utilities/config_plan.py index 228597e9..dc34657a 100644 --- a/nautobot_golden_config/utilities/config_plan.py +++ b/nautobot_golden_config/utilities/config_plan.py @@ -1,4 +1,5 @@ """Functions to support config plan.""" + from nautobot.core.utils.data import render_jinja2 from nautobot.dcim.models import Device from nautobot.extras.models import Status diff --git a/nautobot_golden_config/utilities/config_postprocessing.py b/nautobot_golden_config/utilities/config_postprocessing.py index 4254f882..92421530 100644 --- a/nautobot_golden_config/utilities/config_postprocessing.py +++ b/nautobot_golden_config/utilities/config_postprocessing.py @@ -1,4 +1,5 @@ """Functions related to prepare configuration with postprocessing.""" + from functools import partial from typing import Optional @@ -12,6 +13,7 @@ from nautobot.extras.models.secrets import SecretsGroup from nautobot.users.models import User from netutils.utils import jinja2_convenience_function + from nautobot_golden_config import models from nautobot_golden_config.exceptions import RenderConfigToPushError from nautobot_golden_config.utilities.constant import ENABLE_POSTPROCESSING, PLUGIN_CFG diff --git a/nautobot_golden_config/utilities/constant.py b/nautobot_golden_config/utilities/constant.py index a13703c0..f1f5db28 100644 --- a/nautobot_golden_config/utilities/constant.py +++ b/nautobot_golden_config/utilities/constant.py @@ -1,4 +1,5 @@ """Storage of data that will not change throughout the life cycle of application.""" + from django.conf import settings from django.utils.module_loading import import_string diff --git a/nautobot_golden_config/utilities/db_management.py b/nautobot_golden_config/utilities/db_management.py index b1fb9e32..09433be3 100644 --- a/nautobot_golden_config/utilities/db_management.py +++ b/nautobot_golden_config/utilities/db_management.py @@ -1,8 +1,8 @@ """Functions to manage DB related tasks.""" + from django.db import connections from nautobot_plugin_nornir.constants import NORNIR_SETTINGS - RUNNER_SETTINGS = NORNIR_SETTINGS.get("runner", {}) diff --git a/nautobot_golden_config/utilities/git.py b/nautobot_golden_config/utilities/git.py index f1cabacc..fda680e9 100644 --- a/nautobot_golden_config/utilities/git.py +++ b/nautobot_golden_config/utilities/git.py @@ -1,6 +1,7 @@ """Git helper methods and class.""" import logging + from nautobot.core.utils.git import GitRepo as _GitRepo LOGGER = logging.getLogger(__name__) diff --git a/nautobot_golden_config/utilities/helper.py b/nautobot_golden_config/utilities/helper.py index 77c0f494..f894ec9a 100644 --- a/nautobot_golden_config/utilities/helper.py +++ b/nautobot_golden_config/utilities/helper.py @@ -1,29 +1,28 @@ """Helper functions.""" + # pylint: disable=raise-missing-from import json from copy import deepcopy -from lxml import etree # nosec from django.conf import settings from django.contrib import messages from django.db.models import Q from django.template import engines -from django.utils.html import format_html from django.urls import reverse - +from django.utils.html import format_html from jinja2 import exceptions as jinja_errors from jinja2.sandbox import SandboxedEnvironment +from lxml import etree +from nautobot.core.utils.data import render_jinja2 from nautobot.dcim.filters import DeviceFilterSet from nautobot.dcim.models import Device -from nautobot.core.utils.data import render_jinja2 from nautobot.extras.models import Job from nornir_nautobot.exceptions import NornirNautobotException +from nautobot_golden_config import config as app_config from nautobot_golden_config import models from nautobot_golden_config.utilities import utils from nautobot_golden_config.utilities.constant import JINJA_ENV -from nautobot_golden_config import config as app_config - FRAMEWORK_METHODS = { "default": utils.default_framework, @@ -173,6 +172,7 @@ def render_jinja_template(obj, logger, template): def get_device_to_settings_map(queryset): """Helper function to map settings to devices.""" device_to_settings_map = {} + update_dynamic_groups_cache() for device in queryset: dynamic_group = device.dynamic_groups.exclude(golden_config_setting__isnull=True).order_by( "-golden_config_setting__weight" @@ -194,7 +194,7 @@ def get_xml_config(config): """Helper to parse XML config files.""" try: parser = etree.XMLParser(remove_blank_text=True) - return etree.fromstring(config, parser=parser) # nosec + return etree.fromstring(config, parser=parser) # noqa: S320 except etree.ParseError: return None @@ -203,7 +203,7 @@ def list_to_string(items): """Helper function to set the proper list of items sentence.""" if len(items) == 1: return items[0] - if len(items) == 2: + if len(items) == 2: # noqa: PLR2004 return " and ".join(items) return ", ".join(items[:-1]) + " and " + items[-1] @@ -278,3 +278,9 @@ def get_xml_subtree_with_full_path(config_xml, match_config): current_element = copied_parent current_element.append(deepcopy(element)) return etree.tostring(new_root, encoding="unicode", pretty_print=True) + + +def update_dynamic_groups_cache(): + """Update dynamic group cache for all golden config dynamic groups.""" + for setting in models.GoldenConfigSetting.objects.all(): + setting.dynamic_group.update_cached_members() diff --git a/nautobot_golden_config/utilities/logger.py b/nautobot_golden_config/utilities/logger.py index 4563315d..3ff8dfad 100644 --- a/nautobot_golden_config/utilities/logger.py +++ b/nautobot_golden_config/utilities/logger.py @@ -1,7 +1,7 @@ """Customer logger to support writing to console and db.""" -from typing import Any import logging +from typing import Any LOGGER = logging.getLogger("NORNIR_LOGGER") diff --git a/nautobot_golden_config/utilities/mat_plot.py b/nautobot_golden_config/utilities/mat_plot.py index 4b6b23d6..3397a3e4 100644 --- a/nautobot_golden_config/utilities/mat_plot.py +++ b/nautobot_golden_config/utilities/mat_plot.py @@ -1,15 +1,15 @@ """Utility functions for working with mathplotlib.""" + import base64 import io import logging import urllib -from django.db.models import Count, Q - import matplotlib.pyplot as plt import numpy as np - +from django.db.models import Count, Q from nautobot.core.choices import ColorChoices + from nautobot_golden_config.utilities import constant GREEN = "#" + ColorChoices.COLOR_GREEN diff --git a/nautobot_golden_config/utilities/utils.py b/nautobot_golden_config/utilities/utils.py index 63be40b3..a45ca6eb 100644 --- a/nautobot_golden_config/utilities/utils.py +++ b/nautobot_golden_config/utilities/utils.py @@ -1,8 +1,7 @@ """Utility functions.""" -from django.conf import settings from constance import config as constance_name - +from django.conf import settings from nautobot.extras.choices import SecretsGroupAccessTypeChoices from nautobot.extras.models.secrets import SecretsGroupAssociation diff --git a/nautobot_golden_config/views.py b/nautobot_golden_config/views.py index ad1f601e..76e812e3 100644 --- a/nautobot_golden_config/views.py +++ b/nautobot_golden_config/views.py @@ -1,4 +1,5 @@ """Django views for Nautobot Golden Configuration.""" # pylint: disable=too-many-lines + import json import logging from datetime import datetime @@ -9,14 +10,13 @@ from django.db.models import Count, ExpressionWrapper, F, FloatField, Max, Q from django.shortcuts import redirect, render from django.urls import reverse - from django.utils.html import format_html from django.utils.timezone import make_aware from django.views.generic import View from django_pivot.pivot import pivot from nautobot.apps import views from nautobot.core.views import generic -from nautobot.core.views.mixins import ObjectPermissionRequiredMixin, PERMISSIONS_ACTION_MAP +from nautobot.core.views.mixins import PERMISSIONS_ACTION_MAP, ObjectPermissionRequiredMixin from nautobot.dcim.models import Device from nautobot.extras.models import Job, JobResult from rest_framework.decorators import action diff --git a/poetry.lock b/poetry.lock index 616bc924..5ae165a2 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.8.2 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand. [[package]] name = "amqp" @@ -141,18 +141,21 @@ tests-no-zope = ["cloudpickle", "hypothesis", "mypy (>=1.1.1)", "pympler", "pyte [[package]] name = "babel" -version = "2.12.1" +version = "2.16.0" description = "Internationalization utilities" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "Babel-2.12.1-py3-none-any.whl", hash = "sha256:b4246fb7677d3b98f501a39d43396d3cafdc8eadb045f4a31be01863f655c610"}, - {file = "Babel-2.12.1.tar.gz", hash = "sha256:cc2d99999cd01d44420ae725a21c9e3711b3aadc7976d6147f622d8581963455"}, + {file = "babel-2.16.0-py3-none-any.whl", hash = "sha256:368b5b98b37c06b7daf6696391c3240c938b37767d4584413e8438c5c435fa8b"}, + {file = "babel-2.16.0.tar.gz", hash = "sha256:d1f3554ca26605fe173f3de0c65f750f5a42f924499bf134de6423582298e316"}, ] [package.dependencies] pytz = {version = ">=2015.7", markers = "python_version < \"3.9\""} +[package.extras] +dev = ["freezegun (>=1.0,<2.0)", "pytest (>=6.0)", "pytest-cov"] + [[package]] name = "backcall" version = "0.2.0" @@ -195,29 +198,6 @@ tzdata = {version = "*", optional = true, markers = "extra == \"tzdata\""} [package.extras] tzdata = ["tzdata"] -[[package]] -name = "bandit" -version = "1.7.5" -description = "Security oriented static analyser for python code." -optional = false -python-versions = ">=3.7" -files = [ - {file = "bandit-1.7.5-py3-none-any.whl", hash = "sha256:75665181dc1e0096369112541a056c59d1c5f66f9bb74a8d686c3c362b83f549"}, - {file = "bandit-1.7.5.tar.gz", hash = "sha256:bdfc739baa03b880c2d15d0431b31c658ffc348e907fe197e54e0389dd59e11e"}, -] - -[package.dependencies] -colorama = {version = ">=0.3.9", markers = "platform_system == \"Windows\""} -GitPython = ">=1.0.1" -PyYAML = ">=5.3.1" -rich = "*" -stevedore = ">=1.20.0" - -[package.extras] -test = ["beautifulsoup4 (>=4.8.0)", "coverage (>=4.5.4)", "fixtures (>=3.0.0)", "flake8 (>=4.0.0)", "pylint (==1.9.4)", "stestr (>=2.5.0)", "testscenarios (>=0.5.0)", "testtools (>=2.3.0)", "tomli (>=1.1.0)"] -toml = ["tomli (>=1.1.0)"] -yaml = ["PyYAML"] - [[package]] name = "bcrypt" version = "4.0.1" @@ -254,19 +234,22 @@ typecheck = ["mypy"] [[package]] name = "beautifulsoup4" -version = "4.12.2" +version = "4.12.3" description = "Screen-scraping library" optional = false python-versions = ">=3.6.0" files = [ - {file = "beautifulsoup4-4.12.2-py3-none-any.whl", hash = "sha256:bd2520ca0d9d7d12694a53d44ac482d181b4ec1888909b035a3dbf40d0f57d4a"}, - {file = "beautifulsoup4-4.12.2.tar.gz", hash = "sha256:492bbc69dca35d12daac71c4db1bfff0c876c00ef4a2ffacce226d4638eb72da"}, + {file = "beautifulsoup4-4.12.3-py3-none-any.whl", hash = "sha256:b80878c9f40111313e55da8ba20bdba06d8fa3969fc68304167741bbf9e082ed"}, + {file = "beautifulsoup4-4.12.3.tar.gz", hash = "sha256:74e3d1928edc070d21748185c46e3fb33490f22f52a3addee9aee0f4f7781051"}, ] [package.dependencies] soupsieve = ">1.2" [package.extras] +cchardet = ["cchardet"] +chardet = ["chardet"] +charset-normalizer = ["charset-normalizer"] html5lib = ["html5lib"] lxml = ["lxml"] @@ -281,52 +264,6 @@ files = [ {file = "billiard-4.1.0.tar.gz", hash = "sha256:1ad2eeae8e28053d729ba3373d34d9d6e210f6e4d8bf0a9c64f92bd053f1edf5"}, ] -[[package]] -name = "black" -version = "23.9.1" -description = "The uncompromising code formatter." -optional = false -python-versions = ">=3.8" -files = [ - {file = "black-23.9.1-cp310-cp310-macosx_10_16_arm64.whl", hash = "sha256:d6bc09188020c9ac2555a498949401ab35bb6bf76d4e0f8ee251694664df6301"}, - {file = "black-23.9.1-cp310-cp310-macosx_10_16_universal2.whl", hash = "sha256:13ef033794029b85dfea8032c9d3b92b42b526f1ff4bf13b2182ce4e917f5100"}, - {file = "black-23.9.1-cp310-cp310-macosx_10_16_x86_64.whl", hash = "sha256:75a2dc41b183d4872d3a500d2b9c9016e67ed95738a3624f4751a0cb4818fe71"}, - {file = "black-23.9.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:13a2e4a93bb8ca74a749b6974925c27219bb3df4d42fc45e948a5d9feb5122b7"}, - {file = "black-23.9.1-cp310-cp310-win_amd64.whl", hash = "sha256:adc3e4442eef57f99b5590b245a328aad19c99552e0bdc7f0b04db6656debd80"}, - {file = "black-23.9.1-cp311-cp311-macosx_10_16_arm64.whl", hash = "sha256:8431445bf62d2a914b541da7ab3e2b4f3bc052d2ccbf157ebad18ea126efb91f"}, - {file = "black-23.9.1-cp311-cp311-macosx_10_16_universal2.whl", hash = "sha256:8fc1ddcf83f996247505db6b715294eba56ea9372e107fd54963c7553f2b6dfe"}, - {file = "black-23.9.1-cp311-cp311-macosx_10_16_x86_64.whl", hash = "sha256:7d30ec46de88091e4316b17ae58bbbfc12b2de05e069030f6b747dfc649ad186"}, - {file = "black-23.9.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:031e8c69f3d3b09e1aa471a926a1eeb0b9071f80b17689a655f7885ac9325a6f"}, - {file = "black-23.9.1-cp311-cp311-win_amd64.whl", hash = "sha256:538efb451cd50f43aba394e9ec7ad55a37598faae3348d723b59ea8e91616300"}, - {file = "black-23.9.1-cp38-cp38-macosx_10_16_arm64.whl", hash = "sha256:638619a559280de0c2aa4d76f504891c9860bb8fa214267358f0a20f27c12948"}, - {file = "black-23.9.1-cp38-cp38-macosx_10_16_universal2.whl", hash = "sha256:a732b82747235e0542c03bf352c126052c0fbc458d8a239a94701175b17d4855"}, - {file = "black-23.9.1-cp38-cp38-macosx_10_16_x86_64.whl", hash = "sha256:cf3a4d00e4cdb6734b64bf23cd4341421e8953615cba6b3670453737a72ec204"}, - {file = "black-23.9.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cf99f3de8b3273a8317681d8194ea222f10e0133a24a7548c73ce44ea1679377"}, - {file = "black-23.9.1-cp38-cp38-win_amd64.whl", hash = "sha256:14f04c990259576acd093871e7e9b14918eb28f1866f91968ff5524293f9c573"}, - {file = "black-23.9.1-cp39-cp39-macosx_10_16_arm64.whl", hash = "sha256:c619f063c2d68f19b2d7270f4cf3192cb81c9ec5bc5ba02df91471d0b88c4c5c"}, - {file = "black-23.9.1-cp39-cp39-macosx_10_16_universal2.whl", hash = "sha256:6a3b50e4b93f43b34a9d3ef00d9b6728b4a722c997c99ab09102fd5efdb88325"}, - {file = "black-23.9.1-cp39-cp39-macosx_10_16_x86_64.whl", hash = "sha256:c46767e8df1b7beefb0899c4a95fb43058fa8500b6db144f4ff3ca38eb2f6393"}, - {file = "black-23.9.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:50254ebfa56aa46a9fdd5d651f9637485068a1adf42270148cd101cdf56e0ad9"}, - {file = "black-23.9.1-cp39-cp39-win_amd64.whl", hash = "sha256:403397c033adbc45c2bd41747da1f7fc7eaa44efbee256b53842470d4ac5a70f"}, - {file = "black-23.9.1-py3-none-any.whl", hash = "sha256:6ccd59584cc834b6d127628713e4b6b968e5f79572da66284532525a042549f9"}, - {file = "black-23.9.1.tar.gz", hash = "sha256:24b6b3ff5c6d9ea08a8888f6977eae858e1f340d7260cf56d70a49823236b62d"}, -] - -[package.dependencies] -click = ">=8.0.0" -mypy-extensions = ">=0.4.3" -packaging = ">=22.0" -pathspec = ">=0.9.0" -platformdirs = ">=2" -tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} -typing-extensions = {version = ">=4.0.1", markers = "python_version < \"3.11\""} - -[package.extras] -colorama = ["colorama (>=0.4.3)"] -d = ["aiohttp (>=3.7.4)"] -jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] -uvloop = ["uvloop (>=0.15.2)"] - [[package]] name = "celery" version = "5.3.4" @@ -1361,22 +1298,6 @@ files = [ [package.extras] tests = ["asttokens (>=2.1.0)", "coverage", "coverage-enable-subprocess", "ipython", "littleutils", "pytest", "rich"] -[[package]] -name = "flake8" -version = "5.0.4" -description = "the modular source code checker: pep8 pyflakes and co" -optional = false -python-versions = ">=3.6.1" -files = [ - {file = "flake8-5.0.4-py2.py3-none-any.whl", hash = "sha256:7a1cf6b73744f5806ab95e526f6f0d8c01c66d7bbe349562d22dfca20610b248"}, - {file = "flake8-5.0.4.tar.gz", hash = "sha256:6fbe320aad8d6b95cec8b8e47bc933004678dc63095be98528b7bdd2a9f510db"}, -] - -[package.dependencies] -mccabe = ">=0.7.0,<0.8.0" -pycodestyle = ">=2.9.0,<2.10.0" -pyflakes = ">=2.5.0,<2.6.0" - [[package]] name = "fonttools" version = "4.43.0" @@ -2232,43 +2153,34 @@ importlib-metadata = {version = ">=4.4", markers = "python_version < \"3.10\""} testing = ["coverage", "pyyaml"] [[package]] -name = "markdown-it-py" -version = "3.0.0" -description = "Python port of markdown-it. Markdown parsing, done right!" +name = "markdown-version-annotations" +version = "1.0.1" +description = "Markdown plugin to add custom admonitions for documenting version differences" optional = false -python-versions = ">=3.8" +python-versions = "<4.0,>=3.7" files = [ - {file = "markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb"}, - {file = "markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1"}, + {file = "markdown_version_annotations-1.0.1-py3-none-any.whl", hash = "sha256:6df0b2ac08bab906c8baa425f59fc0fe342fbe8b3917c144fb75914266b33200"}, + {file = "markdown_version_annotations-1.0.1.tar.gz", hash = "sha256:620aade507ef175ccfb2059db152a34c6a1d2add28c2be16ea4de38d742e6132"}, ] [package.dependencies] -mdurl = ">=0.1,<1.0" - -[package.extras] -benchmarking = ["psutil", "pytest", "pytest-benchmark"] -code-style = ["pre-commit (>=3.0,<4.0)"] -compare = ["commonmark (>=0.9,<1.0)", "markdown (>=3.4,<4.0)", "mistletoe (>=1.0,<2.0)", "mistune (>=2.0,<3.0)", "panflute (>=2.3,<3.0)"] -linkify = ["linkify-it-py (>=1,<3)"] -plugins = ["mdit-py-plugins"] -profiling = ["gprof2dot"] -rtd = ["jupyter_sphinx", "mdit-py-plugins", "myst-parser", "pyyaml", "sphinx", "sphinx-copybutton", "sphinx-design", "sphinx_book_theme"] -testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions"] +markdown = ">=3.3.7,<4.0.0" [[package]] name = "markdown2" -version = "2.4.10" +version = "2.5.0" description = "A fast and complete Python implementation of Markdown" optional = false -python-versions = ">=3.5, <4" +python-versions = "<4,>=3.8" files = [ - {file = "markdown2-2.4.10-py2.py3-none-any.whl", hash = "sha256:e6105800483783831f5dc54f827aa5b44eb137ecef5a70293d8ecfbb4109ecc6"}, - {file = "markdown2-2.4.10.tar.gz", hash = "sha256:cdba126d90dc3aef6f4070ac342f974d63f415678959329cc7909f96cc235d72"}, + {file = "markdown2-2.5.0-py2.py3-none-any.whl", hash = "sha256:300d4429b620ebc974ef512339a9e08bc080473f95135a91f33906e24e8280c1"}, + {file = "markdown2-2.5.0.tar.gz", hash = "sha256:9bff02911f8b617b61eb269c4c1a5f9b2087d7ff051604f66a61b63cab30adc2"}, ] [package.extras] -all = ["pygments (>=2.7.3)", "wavedrom"] +all = ["latex2mathml", "pygments (>=2.7.3)", "wavedrom"] code-syntax-highlighting = ["pygments (>=2.7.3)"] +latex = ["latex2mathml"] wavedrom = ["wavedrom"] [[package]] @@ -2434,17 +2346,6 @@ files = [ {file = "mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325"}, ] -[[package]] -name = "mdurl" -version = "0.1.2" -description = "Markdown URL utilities" -optional = false -python-versions = ">=3.7" -files = [ - {file = "mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8"}, - {file = "mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba"}, -] - [[package]] name = "mergedeep" version = "1.3.4" @@ -2539,17 +2440,6 @@ files = [ {file = "mkdocs_material_extensions-1.2.tar.gz", hash = "sha256:27e2d1ed2d031426a6e10d5ea06989d67e90bb02acd588bc5673106b5ee5eedf"}, ] -[[package]] -name = "mkdocs-version-annotations" -version = "1.0.0" -description = "MkDocs plugin to add custom admonitions for documenting version differences" -optional = false -python-versions = ">=3.7,<4.0" -files = [ - {file = "mkdocs-version-annotations-1.0.0.tar.gz", hash = "sha256:6786024b37d27b330fda240b76ebec8e7ce48bd5a9d7a66e99804559d088dffa"}, - {file = "mkdocs_version_annotations-1.0.0-py3-none-any.whl", hash = "sha256:385004eb4a7530dd87a227e08cd907ce7a8fe21fdf297720a4149c511bcf05f5"}, -] - [[package]] name = "mkdocstrings" version = "0.22.0" @@ -3040,17 +2930,6 @@ files = [ {file = "pathspec-0.11.2.tar.gz", hash = "sha256:e0d8d0ac2f12da61956eb2306b69f9469b42f4deb0f3cb6ed47b9cce9996ced3"}, ] -[[package]] -name = "pbr" -version = "5.11.1" -description = "Python Build Reasonableness" -optional = false -python-versions = ">=2.6" -files = [ - {file = "pbr-5.11.1-py2.py3-none-any.whl", hash = "sha256:567f09558bae2b3ab53cb3c1e2e33e726ff3338e7bae3db5dc954b3a44eef12b"}, - {file = "pbr-5.11.1.tar.gz", hash = "sha256:aefc51675b0b533d56bb5fd1c8c6c0522fe31896679882e1c4c63d5e4a0fccb3"}, -] - [[package]] name = "pexpect" version = "4.8.0" @@ -3307,17 +3186,6 @@ files = [ [package.extras] tests = ["pytest"] -[[package]] -name = "pycodestyle" -version = "2.9.1" -description = "Python style guide checker" -optional = false -python-versions = ">=3.6" -files = [ - {file = "pycodestyle-2.9.1-py2.py3-none-any.whl", hash = "sha256:d1735fc58b418fd7c5f658d28d943854f8a849b01a5d0a1e6f3f3fdd0166804b"}, - {file = "pycodestyle-2.9.1.tar.gz", hash = "sha256:2c9607871d58c76354b697b42f5d57e1ada7d261c261efac224b664affdc5785"}, -] - [[package]] name = "pycparser" version = "2.21" @@ -3346,17 +3214,6 @@ netaddr = "*" dev = ["check-manifest", "pep8", "pyflakes", "twine"] test = ["coverage", "mock"] -[[package]] -name = "pyflakes" -version = "2.5.0" -description = "passive checker of Python programs" -optional = false -python-versions = ">=3.6" -files = [ - {file = "pyflakes-2.5.0-py2.py3-none-any.whl", hash = "sha256:4579f67d887f804e67edb544428f264b7b24f435b263c4614f384135cea553d2"}, - {file = "pyflakes-2.5.0.tar.gz", hash = "sha256:491feb020dca48ccc562a8c0cbe8df07ee13078df59813b83959cbdada312ea3"}, -] - [[package]] name = "pygments" version = "2.16.1" @@ -3960,25 +3817,6 @@ requests = ">=2.0.0" [package.extras] rsa = ["oauthlib[signedtoken] (>=3.0.0)"] -[[package]] -name = "rich" -version = "13.6.0" -description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal" -optional = false -python-versions = ">=3.7.0" -files = [ - {file = "rich-13.6.0-py3-none-any.whl", hash = "sha256:2b38e2fe9ca72c9a00170a1a2d20c63c790d0e10ef1fe35eba76e1e7b1d7d245"}, - {file = "rich-13.6.0.tar.gz", hash = "sha256:5c14d22737e6d5084ef4771b62d5d4363165b403455a30a1c8ca39dc7b644bef"}, -] - -[package.dependencies] -markdown-it-py = ">=2.2.0" -pygments = ">=2.13.0,<3.0.0" -typing-extensions = {version = ">=4.0.0,<5.0", markers = "python_version < \"3.9\""} - -[package.extras] -jupyter = ["ipywidgets (>=7.5.1,<9)"] - [[package]] name = "rpds-py" version = "0.10.3" @@ -4151,28 +3989,29 @@ files = [ [[package]] name = "ruff" -version = "0.1.15" +version = "0.5.5" description = "An extremely fast Python linter and code formatter, written in Rust." optional = false python-versions = ">=3.7" files = [ - {file = "ruff-0.1.15-py3-none-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:5fe8d54df166ecc24106db7dd6a68d44852d14eb0729ea4672bb4d96c320b7df"}, - {file = "ruff-0.1.15-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:6f0bfbb53c4b4de117ac4d6ddfd33aa5fc31beeaa21d23c45c6dd249faf9126f"}, - {file = "ruff-0.1.15-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e0d432aec35bfc0d800d4f70eba26e23a352386be3a6cf157083d18f6f5881c8"}, - {file = "ruff-0.1.15-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9405fa9ac0e97f35aaddf185a1be194a589424b8713e3b97b762336ec79ff807"}, - {file = "ruff-0.1.15-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c66ec24fe36841636e814b8f90f572a8c0cb0e54d8b5c2d0e300d28a0d7bffec"}, - {file = "ruff-0.1.15-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:6f8ad828f01e8dd32cc58bc28375150171d198491fc901f6f98d2a39ba8e3ff5"}, - {file = "ruff-0.1.15-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:86811954eec63e9ea162af0ffa9f8d09088bab51b7438e8b6488b9401863c25e"}, - {file = "ruff-0.1.15-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fd4025ac5e87d9b80e1f300207eb2fd099ff8200fa2320d7dc066a3f4622dc6b"}, - {file = "ruff-0.1.15-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b17b93c02cdb6aeb696effecea1095ac93f3884a49a554a9afa76bb125c114c1"}, - {file = "ruff-0.1.15-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:ddb87643be40f034e97e97f5bc2ef7ce39de20e34608f3f829db727a93fb82c5"}, - {file = "ruff-0.1.15-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:abf4822129ed3a5ce54383d5f0e964e7fef74a41e48eb1dfad404151efc130a2"}, - {file = "ruff-0.1.15-py3-none-musllinux_1_2_i686.whl", hash = "sha256:6c629cf64bacfd136c07c78ac10a54578ec9d1bd2a9d395efbee0935868bf852"}, - {file = "ruff-0.1.15-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:1bab866aafb53da39c2cadfb8e1c4550ac5340bb40300083eb8967ba25481447"}, - {file = "ruff-0.1.15-py3-none-win32.whl", hash = "sha256:2417e1cb6e2068389b07e6fa74c306b2810fe3ee3476d5b8a96616633f40d14f"}, - {file = "ruff-0.1.15-py3-none-win_amd64.whl", hash = "sha256:3837ac73d869efc4182d9036b1405ef4c73d9b1f88da2413875e34e0d6919587"}, - {file = "ruff-0.1.15-py3-none-win_arm64.whl", hash = "sha256:9a933dfb1c14ec7a33cceb1e49ec4a16b51ce3c20fd42663198746efc0427360"}, - {file = "ruff-0.1.15.tar.gz", hash = "sha256:f6dfa8c1b21c913c326919056c390966648b680966febcb796cc9d1aaab8564e"}, + {file = "ruff-0.5.5-py3-none-linux_armv6l.whl", hash = "sha256:605d589ec35d1da9213a9d4d7e7a9c761d90bba78fc8790d1c5e65026c1b9eaf"}, + {file = "ruff-0.5.5-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:00817603822a3e42b80f7c3298c8269e09f889ee94640cd1fc7f9329788d7bf8"}, + {file = "ruff-0.5.5-py3-none-macosx_11_0_arm64.whl", hash = "sha256:187a60f555e9f865a2ff2c6984b9afeffa7158ba6e1eab56cb830404c942b0f3"}, + {file = "ruff-0.5.5-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fe26fc46fa8c6e0ae3f47ddccfbb136253c831c3289bba044befe68f467bfb16"}, + {file = "ruff-0.5.5-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4ad25dd9c5faac95c8e9efb13e15803cd8bbf7f4600645a60ffe17c73f60779b"}, + {file = "ruff-0.5.5-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f70737c157d7edf749bcb952d13854e8f745cec695a01bdc6e29c29c288fc36e"}, + {file = "ruff-0.5.5-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:cfd7de17cef6ab559e9f5ab859f0d3296393bc78f69030967ca4d87a541b97a0"}, + {file = "ruff-0.5.5-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a09b43e02f76ac0145f86a08e045e2ea452066f7ba064fd6b0cdccb486f7c3e7"}, + {file = "ruff-0.5.5-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d0b856cb19c60cd40198be5d8d4b556228e3dcd545b4f423d1ad812bfdca5884"}, + {file = "ruff-0.5.5-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3687d002f911e8a5faf977e619a034d159a8373514a587249cc00f211c67a091"}, + {file = "ruff-0.5.5-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:ac9dc814e510436e30d0ba535f435a7f3dc97f895f844f5b3f347ec8c228a523"}, + {file = "ruff-0.5.5-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:af9bdf6c389b5add40d89b201425b531e0a5cceb3cfdcc69f04d3d531c6be74f"}, + {file = "ruff-0.5.5-py3-none-musllinux_1_2_i686.whl", hash = "sha256:d40a8533ed545390ef8315b8e25c4bb85739b90bd0f3fe1280a29ae364cc55d8"}, + {file = "ruff-0.5.5-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:cab904683bf9e2ecbbe9ff235bfe056f0eba754d0168ad5407832928d579e7ab"}, + {file = "ruff-0.5.5-py3-none-win32.whl", hash = "sha256:696f18463b47a94575db635ebb4c178188645636f05e934fdf361b74edf1bb2d"}, + {file = "ruff-0.5.5-py3-none-win_amd64.whl", hash = "sha256:50f36d77f52d4c9c2f1361ccbfbd09099a1b2ea5d2b2222c586ab08885cf3445"}, + {file = "ruff-0.5.5-py3-none-win_arm64.whl", hash = "sha256:3191317d967af701f1b73a31ed5788795936e423b7acce82a2b63e26eb3e89d6"}, + {file = "ruff-0.5.5.tar.gz", hash = "sha256:cc5516bdb4858d972fbc31d246bdb390eab8df1a26e2353be2dbc0c2d7f5421a"}, ] [[package]] @@ -4329,13 +4168,13 @@ saml = ["python3-saml (>=1.5.0)"] [[package]] name = "soupsieve" -version = "2.5" +version = "2.6" description = "A modern CSS selector implementation for Beautiful Soup." optional = false python-versions = ">=3.8" files = [ - {file = "soupsieve-2.5-py3-none-any.whl", hash = "sha256:eaa337ff55a1579b6549dc679565eac1e3d000563bcb1c8ab0d0fefbc0c2cdc7"}, - {file = "soupsieve-2.5.tar.gz", hash = "sha256:5663d5a7b3bfaeee0bc4372e7fc48f9cff4940b3eec54a6451cc5299f1097690"}, + {file = "soupsieve-2.6-py3-none-any.whl", hash = "sha256:e72c4ff06e4fb6e4b5a9f0f55fe6e81514581fca1515028625d0f299c602ccc9"}, + {file = "soupsieve-2.6.tar.gz", hash = "sha256:e2e68417777af359ec65daac1057404a3c8a5455bb8abc36f1a9866ab1a51abb"}, ] [[package]] @@ -4373,20 +4212,6 @@ pure-eval = "*" [package.extras] tests = ["cython", "littleutils", "pygments", "pytest", "typeguard"] -[[package]] -name = "stevedore" -version = "5.1.0" -description = "Manage dynamic plugins for Python applications" -optional = false -python-versions = ">=3.8" -files = [ - {file = "stevedore-5.1.0-py3-none-any.whl", hash = "sha256:8cc040628f3cea5d7128f2e76cf486b2251a4e543c7b938f58d9a377f6694a2d"}, - {file = "stevedore-5.1.0.tar.gz", hash = "sha256:a54534acf9b89bc7ed264807013b505bf07f74dbe4bcfa37d32bd063870b087c"}, -] - -[package.dependencies] -pbr = ">=2.0.0,<2.1.0 || >2.1.0" - [[package]] name = "svgwrite" version = "1.4.3" @@ -4837,4 +4662,4 @@ all = [] [metadata] lock-version = "2.0" python-versions = ">=3.8,<3.12" -content-hash = "3320a0e8fced27e6dac2b0239c9067d73b8e91fe394da3c482543b5beb8ebade" +content-hash = "d3d8429fc0b3c1628cb046a7e153701cf19d6d2ca6b71c8993afefcb764a402a" diff --git a/pyproject.toml b/pyproject.toml index 52410407..5d97e79c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -44,25 +44,22 @@ nautobot-capacity-metrics = "^3.0.1" xmldiff = "^2.6.3" [tool.poetry.group.dev.dependencies] -bandit = "*" -black = "*" coverage = "*" django-debug-toolbar = "*" -flake8 = "*" invoke = "*" ipython = "*" pylint = "*" pylint-django = "*" pylint-nautobot = "*" -ruff = "*" +ruff = "0.5.5" yamllint = "*" Markdown = "*" +# Render custom markdown for version added/changed/remove notes +markdown-version-annotations = "1.0.1" # Rendering docs to HTML mkdocs = "1.5.2" # Material for MkDocs theme mkdocs-material = "9.2.4" -# Render custom markdown for version added/changed/remove notes -mkdocs-version-annotations = "1.0.0" # Automatic documentation from sources, for MkDocs mkdocstrings = "0.22.0" mkdocstrings-python = "1.5.2" @@ -74,29 +71,6 @@ jsonschema = "*" all = [ ] -[tool.black] -line-length = 120 -target-version = ['py38', 'py39', 'py310', 'py311'] -include = '\.pyi?$' -exclude = ''' -( - /( - \.eggs # exclude a few common directories in the - | \.git # root of the project - | \.hg - | \.mypy_cache - | \.tox - | \.venv - | _build - | buck-out - | build - | dist - )/ - | settings.py # This is where you define files that should not be stylized by black - # the root of the project -) -''' - [tool.pylint.master] # Include the pylint_django plugin to avoid spurious warnings about Django patterns extension-pkg-allow-list = ["lxml"] @@ -110,13 +84,11 @@ no-docstring-rgx="^(_|test_|Meta$)" good-names = ["pk"] [tool.pylint.messages_control] -# Line length is enforced by Black, so pylint doesn't need to check it. -# Pylint and Black disagree about how to format multi-line arrays; Black wins. disable = """, line-too-long, too-few-public-methods, duplicate-code, - """ +""" [tool.pylint.miscellaneous] # Don't flag TODO as a failure, let us commit with things that still need to be done in the code @@ -137,6 +109,10 @@ target-version = "py38" [tool.ruff.lint] select = [ "D", # pydocstyle + "F", "E", "W", # flake8 + "PL", # pylint + "S", # bandit + "I", # isort ] ignore = [ # warning: `one-blank-line-before-class` (D203) and `no-blank-line-before-class` (D211) are incompatible. @@ -154,6 +130,7 @@ ignore = [ "D401", # First line of docstring should be in imperative mood "D407", # Missing dashed underline after section "D416", # Section name ends in colon + "E501", # Line too long # Package specific ignores for backwards compatibility "D417", # Missing argument descriptions in Docstrings @@ -162,12 +139,13 @@ ignore = [ [tool.ruff.lint.pydocstyle] convention = "google" -[tool.ruff.per-file-ignores] +[tool.ruff.lint.per-file-ignores] "nautobot_golden_config/migrations/*" = [ - "D", # pydocstyle + "D", ] "nautobot_golden_config/tests/*" = [ - "D", # pydocstyle + "D", + "S" ] [build-system] diff --git a/tasks.py b/tasks.py index 09bc977f..3b01950e 100644 --- a/tasks.py +++ b/tasks.py @@ -159,17 +159,17 @@ def run_command(context, command, **kwargs): # Check if nautobot is running, no need to start another nautobot container to run a command docker_compose_status = "ps --services --filter status=running" results = docker_compose(context, docker_compose_status, hide="out") - if "nautobot" in results.stdout: - compose_command = "exec" - else: - compose_command = "run --rm --entrypoint=''" + command_env_args = "" if "command_env" in kwargs: command_env = kwargs.pop("command_env") for key, value in command_env.items(): - compose_command += f' --env="{key}={value}"' + command_env_args += f' --env="{key}={value}"' - compose_command += f" -- nautobot {command}" + if "nautobot" in results.stdout: + compose_command = f"exec{command_env_args} nautobot {command}" + else: + compose_command = f"run{command_env_args} --rm --entrypoint='{command}' nautobot" pty = kwargs.pop("pty", True) @@ -494,7 +494,12 @@ def dbshell(context, db_name="", input_file="", output_file="", query=""): f"> '{output_file}'" if output_file else "", ] - docker_compose(context, " ".join(command), env=env, pty=not (input_file or output_file or query)) + docker_compose( + context, + " ".join(command), + env=env, + pty=not (input_file or output_file or query), + ) @task( @@ -519,9 +524,11 @@ def import_db(context, db_name="", input_file="dump.sql"): '--execute="', f"DROP DATABASE IF EXISTS {db_name};", f"CREATE DATABASE {db_name};", - "" - if db_name == "$MYSQL_DATABASE" - else f"GRANT ALL PRIVILEGES ON {db_name}.* TO $MYSQL_USER; FLUSH PRIVILEGES;", + ( + "" + if db_name == "$MYSQL_DATABASE" + else f"GRANT ALL PRIVILEGES ON {db_name}.* TO $MYSQL_USER; FLUSH PRIVILEGES;" + ), '"', "&&", "mysql", @@ -647,28 +654,6 @@ def generate_release_notes(context, version=""): # ------------------------------------------------------------------------------ # TESTS # ------------------------------------------------------------------------------ -@task( - help={ - "autoformat": "Apply formatting recommendations automatically, rather than failing if formatting is incorrect.", - } -) -def black(context, autoformat=False): - """Check Python code style with Black.""" - if autoformat: - black_command = "black" - else: - black_command = "black --check --diff" - - command = f"{black_command} ." - - run_command(context, command) - - -@task -def flake8(context): - """Check for PEP8 compliance and other style issues.""" - command = "flake8 . --config .flake8" - run_command(context, command) @task @@ -688,38 +673,39 @@ def pylint(context): @task(aliases=("a",)) def autoformat(context): """Run code autoformatting.""" - black(context, autoformat=True) - ruff(context, fix=True) + ruff(context, action=["format"], fix=True) @task( help={ - "action": "One of 'lint', 'format', or 'both'", - "fix": "Automatically fix selected action. May not be able to fix all.", - "output_format": "see https://docs.astral.sh/ruff/settings/#output-format", + "action": "Available values are `['lint', 'format']`. Can be used multiple times. (default: `['lint', 'format']`)", + "target": "File or directory to inspect, repeatable (default: all files in the project will be inspected)", + "fix": "Automatically fix selected actions. May not be able to fix all issues found. (default: False)", + "output_format": "See https://docs.astral.sh/ruff/settings/#output-format for details. (default: `concise`)", }, + iterable=["action", "target"], ) -def ruff(context, action="lint", fix=False, output_format="text"): +def ruff(context, action=None, target=None, fix=False, output_format="concise"): """Run ruff to perform code formatting and/or linting.""" - if action != "lint": - command = "ruff format" - if not fix: - command += " --check" - command += " ." - run_command(context, command) - if action != "format": - command = "ruff check" - if fix: - command += " --fix" - command += f" --output-format {output_format} ." - run_command(context, command) + if not action: + action = ["lint", "format"] + if not target: + target = ["."] + if "format" in action: + command = "ruff format " + if not fix: + command += "--check " + command += " ".join(target) + run_command(context, command, warn=True) -@task -def bandit(context): - """Run bandit to validate basic static code security analysis.""" - command = "bandit --recursive . --configfile .bandit.yml" - run_command(context, command) + if "lint" in action: + command = "ruff check " + if fix: + command += "--fix " + command += f"--output-format {output_format} " + command += " ".join(target) + run_command(context, command, warn=True) @task @@ -751,7 +737,7 @@ def check_migrations(context): "verbose": "Enable verbose test output.", } ) -def unittest( +def unittest( # noqa: PLR0913 context, keepdb=False, label="nautobot_golden_config", @@ -799,14 +785,8 @@ def tests(context, failfast=False, keepdb=False, lint_only=False): print("Starting Docker Containers...") start(context) # Sorted loosely from fastest to slowest - print("Running black...") - black(context) print("Running ruff...") ruff(context) - print("Running flake8...") - flake8(context) - print("Running bandit...") - bandit(context) print("Running yamllint...") yamllint(context) print("Running poetry check...") @@ -839,11 +819,20 @@ def generate_app_config_schema(context): - `NautobotAppConfig.required_settings` """ start(context, service="nautobot") - nbshell(context, file="development/app_config_schema.py", env={"APP_CONFIG_SCHEMA_COMMAND": "generate"}) + nbshell( + context, + file="development/app_config_schema.py", + env={"APP_CONFIG_SCHEMA_COMMAND": "generate"}, + ) @task def validate_app_config(context): """Validate the app config based on the app config schema.""" start(context, service="nautobot") - nbshell(context, plain=True, file="development/app_config_schema.py", env={"APP_CONFIG_SCHEMA_COMMAND": "validate"}) + nbshell( + context, + plain=True, + file="development/app_config_schema.py", + env={"APP_CONFIG_SCHEMA_COMMAND": "validate"}, + )