diff --git a/.cookiecutter.json b/.cookiecutter.json index d1e2df97..c5f93035 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": "cfd9475f3ea601572d0f3b23d4bb6d9655bc2721" + "baked_commit_ref": "76744d296d468b03fb528c902f68c3dc230a9a3f" } } } 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 17bd2cfa..24473bd9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -16,7 +16,7 @@ env: APP_NAME: "nautobot-app-chatops" jobs: - black: + ruff-format: runs-on: "ubuntu-22.04" env: INVOKE_NAUTOBOT_CHATOPS_LOCAL: "True" @@ -25,20 +25,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_CHATOPS_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_CHATOPS_LOCAL: "True" @@ -60,17 +49,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_CHATOPS_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: @@ -95,12 +73,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/README.md b/README.md index 43a0c439..2988b69f 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@
- A multi-platform ChatOps bot App for Nautobot. + An App for Nautobot.

## Overview diff --git a/changes/321.housekeeping b/changes/321.housekeeping new file mode 100644 index 00000000..996d400a --- /dev/null +++ b/changes/321.housekeeping @@ -0,0 +1 @@ +Rebake with 2.3.0 Cookiecutter. \ No newline at end of file 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 9e17c4ad..5d558da2 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 106be230..a6d0dfc8 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 2f1103da..dbe31cba 100644 --- a/development/docker-compose.mysql.yml +++ b/development/docker-compose.mysql.yml @@ -1,6 +1,4 @@ --- -version: "3.8" - services: nautobot: environment: 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/mattermost/nautobot_bootstrap.py b/development/mattermost/nautobot_bootstrap.py index bdd6462c..3fae7744 100644 --- a/development/mattermost/nautobot_bootstrap.py +++ b/development/mattermost/nautobot_bootstrap.py @@ -1,19 +1,18 @@ """Bootstrap script for Nautobot to allow Mattermost integration.""" - import contextlib + from django.contrib.auth import get_user_model from django.core.exceptions import ObjectDoesNotExist from nautobot_chatops.models import ( - AccessGrantTypeChoices, - PlatformChoices, AccessGrant, - CommandToken, + AccessGrantTypeChoices, ChatOpsAccountLink, + CommandToken, + PlatformChoices, ) - User = get_user_model() for grant_type in AccessGrantTypeChoices.values(): diff --git a/development/nautobot_config.py b/development/nautobot_config.py index 825ea553..31cc69c5 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, } } @@ -171,7 +173,7 @@ "tower_password": os.getenv("NAUTOBOT_TOWER_PASSWORD"), "tower_uri": os.getenv("NAUTOBOT_TOWER_URI"), "tower_username": os.getenv("NAUTOBOT_TOWER_USERNAME"), - "tower_verify_ssl": is_truthy(os.getenv("NAUTOBOT_TOWER_VERIFY_SSL", True)), + "tower_verify_ssl": is_truthy(os.getenv("NAUTOBOT_TOWER_VERIFY_SSL", "true")), # - Arista CloudVision --------------- "enable_aristacv": is_truthy(os.getenv("NAUTOBOT_CHATOPS_ENABLE_ARISTACV")), "aristacv_cvaas_url": os.environ.get("ARISTACV_CVAAS_URL"), 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 d0875111..e6fc5d35 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 621c4ea2..abee82ce 100644 --- a/docs/dev/dev_environment.md +++ b/docs/dev/dev_environment.md @@ -131,10 +131,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. @@ -462,7 +459,7 @@ This is the same as running: ### Tests -To run tests against your code, you can run all the tests that GitHub CI 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 @@ -472,9 +469,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 e3d2c7ec..ca55a90d 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1,6 +1,6 @@ mkdocs==1.5.2 mkdocs-material==9.1.15 -mkdocs-version-annotations==1.0.0 +markdown-version-annotations==1.0.1 mkdocstrings-python==1.5.2 mkdocstrings==0.22.0 mkdocs-include-markdown-plugin==6.0.3 diff --git a/mkdocs.yml b/mkdocs.yml index 25aec33f..c806a12c 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_chatops/__init__.py b/nautobot_chatops/__init__.py index efa39a7b..c8e89ce8 100644 --- a/nautobot_chatops/__init__.py +++ b/nautobot_chatops/__init__.py @@ -1,4 +1,5 @@ """App declaration for nautobot_chatops.""" + # Metadata is inherited from Nautobot. If not including Nautobot in the environment, this should be added from importlib import metadata @@ -158,6 +159,7 @@ def ready(self): super().ready() # pylint: disable=import-outside-toplevel from nautobot_capacity_metrics import register_metric_func + from .metrics_app import metric_commands register_metric_func(metric_commands) diff --git a/nautobot_chatops/admin.py b/nautobot_chatops/admin.py index 53b3f889..02fa2123 100644 --- a/nautobot_chatops/admin.py +++ b/nautobot_chatops/admin.py @@ -1,6 +1,7 @@ """Administrative capabilities for nautobot_chatops app.""" from django.contrib import admin + from .models import ChatOpsAccountLink, CommandLog diff --git a/nautobot_chatops/api/serializers.py b/nautobot_chatops/api/serializers.py index d0cac19d..616ab373 100644 --- a/nautobot_chatops/api/serializers.py +++ b/nautobot_chatops/api/serializers.py @@ -1,8 +1,7 @@ """API Serializers for ChatOps App.""" -from rest_framework import serializers - from nautobot.core.api import NautobotModelSerializer +from rest_framework import serializers from nautobot_chatops.models import AccessGrant, CommandLog, CommandToken diff --git a/nautobot_chatops/api/urls.py b/nautobot_chatops/api/urls.py index 9443a98d..f470beb1 100644 --- a/nautobot_chatops/api/urls.py +++ b/nautobot_chatops/api/urls.py @@ -5,6 +5,7 @@ from django.urls import include, path from nautobot.apps.api import OrderedDefaultRouter from nautobot.apps.config import get_app_settings_or_config + from nautobot_chatops.api.views.generic import ( AccessGrantViewSet, CommandLogViewSet, @@ -13,7 +14,6 @@ ) from nautobot_chatops.api.views.lookup import AccessLookupView, UserEmailLookupView - logger = logging.getLogger(__name__) urlpatterns = [ path("lookup/", AccessLookupView.as_view(), name="access_lookup"), @@ -21,7 +21,7 @@ ] if get_app_settings_or_config("nautobot_chatops", "enable_slack"): - from nautobot_chatops.api.views.slack import SlackSlashCommandView, SlackInteractionView, SlackEventAPIView + from nautobot_chatops.api.views.slack import SlackEventAPIView, SlackInteractionView, SlackSlashCommandView urlpatterns += [ path("slack/slash_command/", SlackSlashCommandView.as_view(), name="slack_slash_command"), @@ -44,7 +44,7 @@ ] if get_app_settings_or_config("nautobot_chatops", "enable_mattermost"): - from nautobot_chatops.api.views.mattermost import MattermostSlashCommandView, MattermostInteractionView + from nautobot_chatops.api.views.mattermost import MattermostInteractionView, MattermostSlashCommandView urlpatterns += [ path("mattermost/slash_command/", MattermostSlashCommandView.as_view(), name="mattermost_slash_command"), diff --git a/nautobot_chatops/api/views/generic.py b/nautobot_chatops/api/views/generic.py index fb26d10e..0cbd85f9 100644 --- a/nautobot_chatops/api/views/generic.py +++ b/nautobot_chatops/api/views/generic.py @@ -1,10 +1,11 @@ """API Views for Nautobot Chatops.""" -from rest_framework.routers import APIRootView + from nautobot.apps.api import NautobotModelViewSet +from rest_framework.routers import APIRootView from nautobot_chatops.api.serializers import AccessGrantSerializer, CommandLogSerializer, CommandTokenSerializer -from nautobot_chatops.models import AccessGrant, CommandLog, CommandToken from nautobot_chatops.filters import AccessGrantFilterSet, CommandLogFilterSet, CommandTokenFilterSet +from nautobot_chatops.models import AccessGrant, CommandLog, CommandToken class NautobotChatopsRootView(APIRootView): diff --git a/nautobot_chatops/api/views/lookup.py b/nautobot_chatops/api/views/lookup.py index 2c93668d..41453bcd 100644 --- a/nautobot_chatops/api/views/lookup.py +++ b/nautobot_chatops/api/views/lookup.py @@ -1,7 +1,8 @@ """API views for dynamic lookup of platform-specific data.""" import contextlib -from django.http import JsonResponse, HttpResponseBadRequest, HttpResponseNotFound + +from django.http import HttpResponseBadRequest, HttpResponseNotFound, JsonResponse from django.views import View from nautobot_chatops.dispatchers import Dispatcher diff --git a/nautobot_chatops/api/views/mattermost.py b/nautobot_chatops/api/views/mattermost.py index 05ab58f7..25e0013f 100644 --- a/nautobot_chatops/api/views/mattermost.py +++ b/nautobot_chatops/api/views/mattermost.py @@ -10,12 +10,12 @@ from django.views import View from django.views.decorators.csrf import csrf_exempt -from nautobot_chatops.workers import get_commands_registry, commands_help, parse_command_string -from nautobot_chatops.dispatchers.mattermost import MattermostDispatcher, Driver -from nautobot_chatops.utils import check_and_enqueue_command +from nautobot_chatops.choices import PlatformChoices +from nautobot_chatops.dispatchers.mattermost import Driver, MattermostDispatcher from nautobot_chatops.metrics import signature_error_cntr from nautobot_chatops.models import CommandToken -from nautobot_chatops.choices import PlatformChoices +from nautobot_chatops.utils import check_and_enqueue_command +from nautobot_chatops.workers import commands_help, get_commands_registry, parse_command_string # pylint: disable=logging-fstring-interpolation diff --git a/nautobot_chatops/api/views/ms_teams.py b/nautobot_chatops/api/views/ms_teams.py index abb75bcc..a2305db9 100644 --- a/nautobot_chatops/api/views/ms_teams.py +++ b/nautobot_chatops/api/views/ms_teams.py @@ -1,22 +1,20 @@ """Views to receive inbound notifications from Microsoft Teams, parse them, and enqueue worker actions.""" import json -import re import logging +import re -import requests import jwt - +import requests from django.conf import settings from django.http import HttpResponse from django.utils.decorators import method_decorator from django.views import View from django.views.decorators.csrf import csrf_exempt -from nautobot_chatops.workers import get_commands_registry, commands_help, parse_command_string from nautobot_chatops.dispatchers.ms_teams import MSTeamsDispatcher from nautobot_chatops.utils import check_and_enqueue_command - +from nautobot_chatops.workers import commands_help, get_commands_registry, parse_command_string logger = logging.getLogger(__name__) diff --git a/nautobot_chatops/api/views/slack.py b/nautobot_chatops/api/views/slack.py index 9110bbdb..b390e4b6 100644 --- a/nautobot_chatops/api/views/slack.py +++ b/nautobot_chatops/api/views/slack.py @@ -13,10 +13,10 @@ from django.views.decorators.csrf import csrf_exempt from slack_sdk import WebClient -from nautobot_chatops.workers import get_commands_registry, commands_help, parse_command_string from nautobot_chatops.dispatchers.slack import SlackDispatcher -from nautobot_chatops.utils import check_and_enqueue_command from nautobot_chatops.metrics import signature_error_cntr +from nautobot_chatops.utils import check_and_enqueue_command +from nautobot_chatops.workers import commands_help, get_commands_registry, parse_command_string # pylint: disable=logging-fstring-interpolation @@ -194,10 +194,12 @@ def post(self, request, *args, **kwargs): except ValueError as err: logger.error("%s", err) return HttpResponse(f"Error: {err} encountered when processing {callback_id}") + # If more than 2 arguments are provided, we will need to format the selected value + argument_check = 2 for i, cmd in enumerate(cmds): - if i == 2: + if i == argument_check: selected_value += f"'{cmd}'" - elif i > 2: + elif i > argument_check: selected_value += f" '{cmd}'" action_id = f"{cmds[0]} {cmds[1]}" diff --git a/nautobot_chatops/api/views/webex.py b/nautobot_chatops/api/views/webex.py index 0e014e52..bf8e7b5d 100644 --- a/nautobot_chatops/api/views/webex.py +++ b/nautobot_chatops/api/views/webex.py @@ -11,14 +11,12 @@ from django.utils.decorators import method_decorator from django.views import View from django.views.decorators.csrf import csrf_exempt - from webexteamssdk import WebexTeamsAPI from webexteamssdk.exceptions import AccessTokenError, ApiError -from nautobot_chatops.workers import get_commands_registry, commands_help, parse_command_string -from nautobot_chatops.dispatchers.webex import WebexDispatcher -from nautobot_chatops.dispatchers.webex import WEBEX_CONFIG +from nautobot_chatops.dispatchers.webex import WEBEX_CONFIG, WebexDispatcher from nautobot_chatops.utils import check_and_enqueue_command +from nautobot_chatops.workers import commands_help, get_commands_registry, parse_command_string logger = logging.getLogger(__name__) diff --git a/nautobot_chatops/banner.py b/nautobot_chatops/banner.py index 9d59de72..83daa7ca 100644 --- a/nautobot_chatops/banner.py +++ b/nautobot_chatops/banner.py @@ -1,9 +1,9 @@ """Banner to alert Staff to use Admin site if trying to add/edit Account Links for other Users.""" + from typing import Optional from django.urls import reverse from django.utils.html import format_html - from nautobot.extras.choices import BannerClassChoices from nautobot.extras.plugins import PluginBanner diff --git a/nautobot_chatops/constants.py b/nautobot_chatops/constants.py index 22f46662..0b5cf354 100644 --- a/nautobot_chatops/constants.py +++ b/nautobot_chatops/constants.py @@ -13,8 +13,6 @@ # pylint: disable=line-too-long ACCESS_GRANT_VALUE_HELP_TEXT = "Corresponding ID value to grant access to.
Enter * to grant access to all organizations, channels, or users" -COMMAND_TOKEN_COMMENT_HELP_TEXT = "Optional: Enter description of token" # nosec - skips Bandit B105 error -COMMAND_TOKEN_TOKEN_HELP_TEXT = ( - "Token given by chat platform for signing or command validation" # nosec - skips Bandit B105 error -) +COMMAND_TOKEN_COMMENT_HELP_TEXT = "Optional: Enter description of token" # noqa S105 - skips Ruff S105 error +COMMAND_TOKEN_TOKEN_HELP_TEXT = "Token given by chat platform for signing or command validation" # noqa S105 - skips Ruff S105 error CHATOPS_USER_ID_HELP_TEXT = "Enter the chat platform's User ID you want to link." diff --git a/nautobot_chatops/dispatchers/adaptive_cards.py b/nautobot_chatops/dispatchers/adaptive_cards.py index e1dd5ff3..acefefc1 100644 --- a/nautobot_chatops/dispatchers/adaptive_cards.py +++ b/nautobot_chatops/dispatchers/adaptive_cards.py @@ -1,4 +1,5 @@ """Dispatcher subclass for chat platforms that use Adaptive Cards (https://adaptivecards.io/).""" + from .base import Dispatcher # pylint: disable=abstract-method @@ -152,9 +153,7 @@ def prompt_for_text(self, action_id, help_text, label, title="Your attention ple blocks = [self.markdown_block(help_text), self.actions_block("TODO", [textentry, buttons])] return self.send_blocks(blocks, ephemeral=True, title=title) - def prompt_from_menu( - self, action_id, help_text, choices, default=(None, None), confirm=False, offset=0 - ): # pylint: disable=too-many-arguments + def prompt_from_menu(self, action_id, help_text, choices, default=(None, None), confirm=False, offset=0): # noqa: PLR0913, pylint: disable=too-many-arguments """Prompt the user to make a selection from a menu of choices. Args: diff --git a/nautobot_chatops/dispatchers/base.py b/nautobot_chatops/dispatchers/base.py index 2bbf4836..905e7244 100644 --- a/nautobot_chatops/dispatchers/base.py +++ b/nautobot_chatops/dispatchers/base.py @@ -1,17 +1,18 @@ """Generic base class modeling the API for sending messages to a generic chat platform.""" + import logging from typing import Dict, Optional -from django.templatetags.static import static + +from django.conf import settings from django.contrib.auth import get_user_model from django.core.cache import cache from django.core.exceptions import ObjectDoesNotExist -from django.conf import settings +from django.templatetags.static import static from nautobot.apps.config import get_app_settings_or_config from texttable import Texttable from nautobot_chatops.models import ChatOpsAccountLink - logger = logging.getLogger(__name__) _APP_CONFIG: Dict = settings.PLUGINS_CONFIG["nautobot_chatops"] @@ -118,16 +119,16 @@ def subclasses(cls): # TODO: this should be dynamic using entry_points # pylint: disable=import-outside-toplevel, unused-import, cyclic-import if get_app_settings_or_config("nautobot_chatops", "enable_slack"): - from .slack import SlackDispatcher + from .slack import SlackDispatcher # noqa: F401 if get_app_settings_or_config("nautobot_chatops", "enable_ms_teams"): - from .ms_teams import MSTeamsDispatcher + from .ms_teams import MSTeamsDispatcher # noqa: F401 if get_app_settings_or_config("nautobot_chatops", "enable_webex"): - from .webex import WebexDispatcher + from .webex import WebexDispatcher # noqa: F401 if get_app_settings_or_config("nautobot_chatops", "enable_mattermost"): - from .mattermost import MattermostDispatcher + from .mattermost import MattermostDispatcher # noqa: F401 subclasses = set() classes = [cls] @@ -304,9 +305,7 @@ def prompt_for_text(self, action_id, help_text, label, title="Your attention ple """ raise NotImplementedError - def prompt_from_menu( - self, action_id, help_text, choices, default=(None, None), confirm=False, offset=0 - ): # pylint: disable=too-many-arguments + def prompt_from_menu(self, action_id, help_text, choices, default=(None, None), confirm=False, offset=0): # pylint: disable=too-many-arguments """Prompt the user to make a selection from a menu of choices. Args: diff --git a/nautobot_chatops/dispatchers/mattermost.py b/nautobot_chatops/dispatchers/mattermost.py index 5303f2a1..12d752cb 100644 --- a/nautobot_chatops/dispatchers/mattermost.py +++ b/nautobot_chatops/dispatchers/mattermost.py @@ -3,13 +3,15 @@ import json import logging import time +from http import HTTPStatus from typing import Dict, Optional -import requests -from requests.exceptions import HTTPError +import requests from django.conf import settings +from requests.exceptions import HTTPError from nautobot_chatops.metrics import backend_action_sum + from .base import Dispatcher logger = logging.getLogger(__name__) @@ -77,33 +79,33 @@ def inner(*args, **kwargs): # pylint: disable=inconsistent-return-statements try: return function(*args, **kwargs) except HTTPError as err: - if err.response.status_code == 400: + if err.response.status_code == HTTPStatus.BAD_REQUEST: raise BadRequestException(f"Malformatted requests: {err.response.text}") - if err.response.status_code == 401: + if err.response.status_code == HTTPStatus.UNAUTHORIZED: raise UnauthorizedException(f"Invalid credentials provided or account is locked: {err.response.text}") - if err.response.status_code == 403: + if err.response.status_code == HTTPStatus.FORBIDDEN: raise ForbiddenException( f"Insufficient permissions to execute request (ie, any POST method as a regular user): {err.response.text}" ) - if err.response.status_code == 404: + if err.response.status_code == HTTPStatus.NOT_FOUND: raise NotFoundException(f"Attempting to access an endpoint that does not exist: {err.response.text}") - if err.response.status_code == 405: + if err.response.status_code == HTTPStatus.METHOD_NOT_ALLOWED: raise MethodNotAllowedException( f"Wrong request type for target endpoint (ie, POSTing data to a GET endpoint): {err.response.text}" ) - if err.response.status_code == 406: + if err.response.status_code == HTTPStatus.NOT_ACCEPTABLE: raise NotAcceptableException( f"Content Type of the data returned does not match the Accept header of the request: {err.response.text}" ) - if err.response.status_code == 415: + if err.response.status_code == HTTPStatus.UNSUPPORTED_MEDIA_TYPE: raise UnsupportedMediaTypeException(f"Attempting to POST data in incorrect format: {err.response.text}") - if err.response.status_code == 429: + if err.response.status_code == HTTPStatus.TOO_MANY_REQUESTS: raise MMRateLimit( f"You have exceeded the max number of requests per 1-minute period: {err.response.text}" ) - if err.response.status_code == 500: + if err.response.status_code == HTTPStatus.INTERNAL_SERVER_ERROR: raise InternalServerErrorException(f"Contact support if you see this error type: {err.response.text}") - if err.response.status_code == 503: + if err.response.status_code == HTTPStatus.SERVICE_UNAVAILABLE: raise ServiceUnavailableException( f"The Mattermost API is currently in maintenance mode: {err.response.text}" ) @@ -493,9 +495,7 @@ def prompt_for_text(self, action_id, help_text, label, title="Your attention ple # In Mattermost, a textentry element can ONLY be sent in a modal Interactive dialog return self.send_blocks(blocks, callback_id=action_id, ephemeral=False, modal=True, title=title) - def prompt_from_menu( - self, action_id, help_text, choices, default=(None, None), confirm=False, offset=0 - ): # pylint: disable=too-many-arguments + def prompt_from_menu(self, action_id, help_text, choices, default=(None, None), confirm=False, offset=0): # pylint: disable=too-many-arguments """Prompt the user for a selection from a menu. Args: @@ -659,7 +659,7 @@ def select_element_interactive(self, action_id, choices, default=(None, None), c Args: action_id (str): Identifying string to associate with this element choices (list): List of (display, value) tuples - default (tuple: Default (display, value) to preselect + default (tuple): Default (display, value) to preselect confirm (bool): If true (and the platform supports it), prompt the user to confirm their selection optional (bool): If set to True, the field will return NoneType is not specified. diff --git a/nautobot_chatops/dispatchers/ms_teams.py b/nautobot_chatops/dispatchers/ms_teams.py index 563461fb..96a510a2 100644 --- a/nautobot_chatops/dispatchers/ms_teams.py +++ b/nautobot_chatops/dispatchers/ms_teams.py @@ -1,9 +1,10 @@ """Dispatcher implementation for sending content to Microsoft Teams.""" -import os + import logging +import os from typing import Optional -import requests +import requests from django.conf import settings from django.contrib.auth import get_user_model from django.core.exceptions import ObjectDoesNotExist @@ -11,6 +12,7 @@ from nautobot_chatops.metrics import backend_action_sum from nautobot_chatops.models import ChatOpsAccountLink + from .adaptive_cards import AdaptiveCardsDispatcher logger = logging.getLogger(__name__) diff --git a/nautobot_chatops/dispatchers/slack.py b/nautobot_chatops/dispatchers/slack.py index 82ce52fc..b2507f61 100644 --- a/nautobot_chatops/dispatchers/slack.py +++ b/nautobot_chatops/dispatchers/slack.py @@ -4,15 +4,16 @@ import logging import os import time - from typing import Optional + from django.conf import settings from django.templatetags.static import static from slack_sdk import WebClient -from slack_sdk.webhook.client import WebhookClient from slack_sdk.errors import SlackApiError, SlackClientError +from slack_sdk.webhook.client import WebhookClient from nautobot_chatops.metrics import backend_action_sum + from .base import Dispatcher logger = logging.getLogger(__name__) @@ -371,9 +372,7 @@ def get_prompt_from_menu_choices(self, choices, offset=0): choices.append(("Next...", f"menu_offset-{new_offset}")) return choices - def prompt_from_menu( - self, action_id, help_text, choices, default=(None, None), confirm=False, offset=0 - ): # pylint: disable=too-many-arguments + def prompt_from_menu(self, action_id, help_text, choices, default=(None, None), confirm=False, offset=0): # pylint: disable=too-many-arguments """Prompt the user for a selection from a menu. Args: diff --git a/nautobot_chatops/dispatchers/webex.py b/nautobot_chatops/dispatchers/webex.py index 3bc7eb9e..34904b2a 100644 --- a/nautobot_chatops/dispatchers/webex.py +++ b/nautobot_chatops/dispatchers/webex.py @@ -1,10 +1,11 @@ """Dispatcher implementation for sending content to Webex.""" -import logging +import logging from typing import Optional + +from texttable import Texttable from webexteamssdk import WebexTeamsAPI from webexteamssdk.exceptions import ApiError -from texttable import Texttable from nautobot_chatops.metrics import backend_action_sum from nautobot_chatops.utils import get_app_config_part diff --git a/nautobot_chatops/filters.py b/nautobot_chatops/filters.py index 9ebd5f4f..040783ab 100644 --- a/nautobot_chatops/filters.py +++ b/nautobot_chatops/filters.py @@ -4,7 +4,7 @@ from nautobot.apps.filters import BaseFilterSet, NautobotFilterSet, SearchFilter from nautobot_chatops.choices import PlatformChoices -from nautobot_chatops.models import CommandLog, AccessGrant, ChatOpsAccountLink, CommandToken +from nautobot_chatops.models import AccessGrant, ChatOpsAccountLink, CommandLog, CommandToken class CommandLogFilterSet(BaseFilterSet): diff --git a/nautobot_chatops/forms.py b/nautobot_chatops/forms.py index e055dae8..0f1028e3 100644 --- a/nautobot_chatops/forms.py +++ b/nautobot_chatops/forms.py @@ -1,13 +1,12 @@ """Forms for Nautobot.""" from django import forms - from nautobot.core.forms import BootstrapMixin, StaticSelect2Multiple from nautobot.extras.forms import NautobotFilterForm -from .models import AccessGrant, CommandLog, CommandToken, ChatOpsAccountLink from .choices import AccessGrantTypeChoices, PlatformChoices from .constants import ACCESS_GRANT_COMMAND_HELP_TEXT, COMMAND_TOKEN_TOKEN_HELP_TEXT +from .models import AccessGrant, ChatOpsAccountLink, CommandLog, CommandToken BLANK_CHOICE = (("", "--------"),) diff --git a/nautobot_chatops/integrations/aci/aci.py b/nautobot_chatops/integrations/aci/aci.py index 572401fa..adb5493b 100644 --- a/nautobot_chatops/integrations/aci/aci.py +++ b/nautobot_chatops/integrations/aci/aci.py @@ -1,14 +1,14 @@ """All interactions with aci.""" -import sys import logging -from datetime import datetime -from datetime import timedelta import re +import sys +from datetime import datetime, timedelta + import requests import urllib3 -from .utils import tenant_from_dn, ap_from_dn +from .utils import ap_from_dn, tenant_from_dn urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) diff --git a/nautobot_chatops/integrations/aci/utils.py b/nautobot_chatops/integrations/aci/utils.py index 64869bdb..07a83049 100644 --- a/nautobot_chatops/integrations/aci/utils.py +++ b/nautobot_chatops/integrations/aci/utils.py @@ -1,6 +1,8 @@ """ACI ChatOps Utilities.""" + import logging import re + from prettytable import PrettyTable logger = logging.getLogger("nautobot") diff --git a/nautobot_chatops/integrations/aci/worker.py b/nautobot_chatops/integrations/aci/worker.py index cb2652bf..bb74ca15 100644 --- a/nautobot_chatops/integrations/aci/worker.py +++ b/nautobot_chatops/integrations/aci/worker.py @@ -5,9 +5,10 @@ from nautobot.core.settings_funcs import is_truthy from nautobot_chatops.choices import CommandStatusChoices -from nautobot_chatops.workers import subcommand_of, handle_subcommands -from .aci import RequestConnectError, RequestHTTPError, NautobotPluginChatopsAci -from .utils import logger, send_logo, build_table, send_wait_msg +from nautobot_chatops.workers import handle_subcommands, subcommand_of + +from .aci import NautobotPluginChatopsAci, RequestConnectError, RequestHTTPError +from .utils import build_table, logger, send_logo, send_wait_msg PLUGIN_SETTINGS = settings.PLUGINS_CONFIG["nautobot_chatops"] diff --git a/nautobot_chatops/integrations/ansible/tower.py b/nautobot_chatops/integrations/ansible/tower.py index a8d07914..02052ebd 100644 --- a/nautobot_chatops/integrations/ansible/tower.py +++ b/nautobot_chatops/integrations/ansible/tower.py @@ -1,10 +1,11 @@ """All interactions with Ansible AWX/Tower.""" + import json import logging from urllib.parse import urlparse -from django.conf import settings import requests +from django.conf import settings logger = logging.getLogger(__name__) @@ -92,6 +93,7 @@ def _get_tower(self, api_path, **kwargs): Args: api_path (str): API path to get data from + **kwargs: Additional Keyword Arguments Returns: (JSON): JSON data for the response @@ -154,7 +156,7 @@ def get_tower_inventory_hosts(self, group_id): """Gets hosts for a given Tower inventory. Args: - group (str): Group Name + group_id (str): Group Name Returns: (json): JSON data of the Tower hosts diff --git a/nautobot_chatops/integrations/ansible/worker.py b/nautobot_chatops/integrations/ansible/worker.py index 61c4509c..cd2af942 100644 --- a/nautobot_chatops/integrations/ansible/worker.py +++ b/nautobot_chatops/integrations/ansible/worker.py @@ -1,10 +1,12 @@ """Worker functions implementing Nautobot "ansible" command and subcommands.""" + import json import logging from collections import namedtuple import yaml from django.conf import settings + from nautobot_chatops.workers import handle_subcommands, subcommand_of from .tower import Tower diff --git a/nautobot_chatops/integrations/cloudvision/cvpgrpcutils.py b/nautobot_chatops/integrations/cloudvision/cvpgrpcutils.py index 7a0ca77d..a53a9bf9 100644 --- a/nautobot_chatops/integrations/cloudvision/cvpgrpcutils.py +++ b/nautobot_chatops/integrations/cloudvision/cvpgrpcutils.py @@ -1,12 +1,13 @@ """Utilities for using GRPC with Cloudvision Chatbot.""" + import ssl -import requests -import grpc import arista.tag.v1 as tag +import grpc +import requests from google.protobuf import wrappers_pb2 as wrappers -from .utils import CVAAS_ADDR -from .utils import DEFAULT_TIMEOUT + +from .utils import CVAAS_ADDR, DEFAULT_TIMEOUT def connect_cv(settings): @@ -27,7 +28,7 @@ def connect_cv(settings): response = requests.post( f"https://{cvp_host}/cvpservice/login/authenticate.do", auth=(username, password), - verify=False, # nosec + verify=False, # noqa: S501 timeout=DEFAULT_TIMEOUT, ) # Otherwise, the server is expected to have a valid certificate signed by a well-known CA. diff --git a/nautobot_chatops/integrations/cloudvision/utils.py b/nautobot_chatops/integrations/cloudvision/utils.py index 456987f3..38f5b28f 100644 --- a/nautobot_chatops/integrations/cloudvision/utils.py +++ b/nautobot_chatops/integrations/cloudvision/utils.py @@ -1,12 +1,14 @@ """Utilities for cloudvision chatbot.""" + import os import ssl from datetime import datetime + import requests -from google.protobuf.timestamp_pb2 import Timestamp # pylint: disable=no-name-in-module from cloudvision.Connector.grpc_client import GRPCClient, create_query from cvprac.cvp_client import CvpClient from django.conf import settings +from google.protobuf.timestamp_pb2 import Timestamp # pylint: disable=no-name-in-module fullpath = os.path.abspath(__file__) directory = os.path.dirname(fullpath) @@ -43,7 +45,7 @@ def get(name): CVP_HOST = CONFIG["cvp_host"] CVP_INSECURE = CONFIG["cvp_insecure"] ON_PREM = CONFIG["on_prem"] -CVP_TOKEN_PATH = "token.txt" # nosec +CVP_TOKEN_PATH = "token.txt" # noqa: S105 CRT_FILE_PATH = "cvp.crt" @@ -559,7 +561,7 @@ def get_token_crt(): request = requests.post( f"https://{CVP_HOST}/cvpservice/login/authenticate.do", auth=(CVP_USERNAME, CVP_PASSWORD), - verify=False, # nosec + verify=False, # noqa: S501 timeout=DEFAULT_TIMEOUT, ) else: diff --git a/nautobot_chatops/integrations/cloudvision/worker.py b/nautobot_chatops/integrations/cloudvision/worker.py index 27b50ddf..1d033cde 100644 --- a/nautobot_chatops/integrations/cloudvision/worker.py +++ b/nautobot_chatops/integrations/cloudvision/worker.py @@ -1,38 +1,40 @@ """Cloudvision chatops.""" + import logging -from datetime import datetime, timedelta import os -from nautobot_chatops.workers import subcommand_of, handle_subcommands # pylint: disable=import-error +from datetime import datetime, timedelta + from nautobot_chatops.choices import CommandStatusChoices # pylint: disable=import-error +from nautobot_chatops.workers import handle_subcommands, subcommand_of # pylint: disable=import-error + from .cvpgrpcutils import get_device_tags from .utils import ( CONFIG, - prompt_for_events_filter, - prompt_for_device_or_container, + get_active_events_data, + get_active_events_data_filter, + get_active_severity_types, + get_applied_configlets_container_id, + get_applied_configlets_device_id, + get_bug_device_report, + get_bug_info, + get_cloudvision_configlets_names, get_cloudvision_container_devices, get_cloudvision_containers, - get_cloudvision_configlets_names, - get_configlet_config, get_cloudvision_devices_all, get_cloudvision_devices_all_resource, get_cloudvision_devices_by_sn, - get_device_id_from_hostname, - get_device_running_configuration, - get_cloudvision_tasks, get_cloudvision_task_logs, + get_cloudvision_tasks, + get_configlet_config, get_container_id_by_name, - get_applied_configlets_container_id, - get_applied_configlets_device_id, - get_severity_choices, - get_active_events_data, - get_active_events_data_filter, - get_active_severity_types, get_device_bugs_data, - get_bug_info, - get_bug_device_report, + get_device_id_from_hostname, + get_device_running_configuration, + get_severity_choices, + prompt_for_device_or_container, + prompt_for_events_filter, ) - logger = logging.getLogger(__name__) dir_path = os.path.dirname(os.path.realpath(__file__)) CLOUDVISION_LOGO_PATH = "nautobot_cloudvision/cloudvision_logo.png" @@ -53,13 +55,12 @@ def check_credentials(dispatcher): "and ARISTACV_CVP_URL are set and your nautobot config file is updated." ) return False - else: - if not CONFIG.get("cvaas_token"): - dispatcher.send_warning( - "Please ensure environment variable ARISTACV_CVAAS_TOKEN " - "is set and your nautobot config file is updated." - ) - return False + elif not CONFIG.get("cvaas_token"): + dispatcher.send_warning( + "Please ensure environment variable ARISTACV_CVAAS_TOKEN " + "is set and your nautobot config file is updated." + ) + return False return True diff --git a/nautobot_chatops/integrations/grafana/api/urls.py b/nautobot_chatops/integrations/grafana/api/urls.py index 2ccef219..ef337c21 100644 --- a/nautobot_chatops/integrations/grafana/api/urls.py +++ b/nautobot_chatops/integrations/grafana/api/urls.py @@ -5,7 +5,6 @@ from nautobot_chatops.integrations.grafana.api.views.generic import NautobotPluginChatopsGrafanaRootView - urlpatterns = [] if get_app_settings_or_config("nautobot_chatops", "enable_grafana"): router = OrderedDefaultRouter() diff --git a/nautobot_chatops/integrations/grafana/api/views/generic.py b/nautobot_chatops/integrations/grafana/api/views/generic.py index da24c2cb..62d47b05 100644 --- a/nautobot_chatops/integrations/grafana/api/views/generic.py +++ b/nautobot_chatops/integrations/grafana/api/views/generic.py @@ -1,4 +1,5 @@ """API Views for Nautobot App Chatops Grafana.""" + from rest_framework.routers import APIRootView diff --git a/nautobot_chatops/integrations/grafana/diffsync/models.py b/nautobot_chatops/integrations/grafana/diffsync/models.py index 772d7f08..7f0180ec 100644 --- a/nautobot_chatops/integrations/grafana/diffsync/models.py +++ b/nautobot_chatops/integrations/grafana/diffsync/models.py @@ -1,8 +1,11 @@ """DiffSync model definitions for Grafana Dashboards.""" -from typing import Optional, List + +from typing import List, Optional + from diffsync import DiffSync, DiffSyncModel -from nautobot_chatops.integrations.grafana.models import Dashboard, Panel, PanelVariable + from nautobot_chatops.integrations.grafana.helpers import format_command +from nautobot_chatops.integrations.grafana.models import Dashboard, Panel, PanelVariable class DashboardModel(DiffSyncModel): diff --git a/nautobot_chatops/integrations/grafana/diffsync/sync.py b/nautobot_chatops/integrations/grafana/diffsync/sync.py index 45c1fc68..b7b00149 100644 --- a/nautobot_chatops/integrations/grafana/diffsync/sync.py +++ b/nautobot_chatops/integrations/grafana/diffsync/sync.py @@ -1,16 +1,19 @@ """Synchronization functions for the implemented DiffSync models.""" + from typing import Union + from diffsync import DiffSyncFlags -from nautobot_chatops.integrations.grafana.grafana import handler -from nautobot_chatops.integrations.grafana.models import Dashboard, Panel, PanelVariable + from nautobot_chatops.integrations.grafana.diffsync.models import ( - NautobotDashboard, GrafanaDashboard, - NautobotPanel, GrafanaPanel, - NautobotVariable, GrafanaVariable, + NautobotDashboard, + NautobotPanel, + NautobotVariable, ) +from nautobot_chatops.integrations.grafana.grafana import handler +from nautobot_chatops.integrations.grafana.models import Dashboard, Panel, PanelVariable def run_dashboard_sync(overwrite: bool = False) -> Union[str, None]: diff --git a/nautobot_chatops/integrations/grafana/exceptions.py b/nautobot_chatops/integrations/grafana/exceptions.py index 38975e7d..e3af5c9c 100644 --- a/nautobot_chatops/integrations/grafana/exceptions.py +++ b/nautobot_chatops/integrations/grafana/exceptions.py @@ -1,6 +1,7 @@ """Nautobot App ChatOps Grafana Exceptions.""" -from pydantic import ValidationError + from isodate import ISO8601Error +from pydantic import ValidationError class DefaultArgsError(BaseException): diff --git a/nautobot_chatops/integrations/grafana/filters.py b/nautobot_chatops/integrations/grafana/filters.py index 51e4aa6b..d6fbb685 100644 --- a/nautobot_chatops/integrations/grafana/filters.py +++ b/nautobot_chatops/integrations/grafana/filters.py @@ -1,7 +1,7 @@ """Filtering for nautobot_plugin_device_lifecycle_mgmt UI.""" -from django_filters import FilterSet, CharFilter from django.db.models import Q +from django_filters import CharFilter, FilterSet from nautobot_chatops.integrations.grafana.models import Dashboard, Panel, PanelVariable diff --git a/nautobot_chatops/integrations/grafana/forms.py b/nautobot_chatops/integrations/grafana/forms.py index 0afcacf6..84f82b45 100644 --- a/nautobot_chatops/integrations/grafana/forms.py +++ b/nautobot_chatops/integrations/grafana/forms.py @@ -1,17 +1,18 @@ """Forms for Nautobot.""" +from django.core.serializers.json import DjangoJSONEncoder from django.forms import ( - ModelForm, + BooleanField, CharField, IntegerField, - BooleanField, JSONField, ModelChoiceField, + ModelForm, ModelMultipleChoiceField, MultipleHiddenInput, ) -from django.core.serializers.json import DjangoJSONEncoder from nautobot.core.forms import BootstrapMixin, BulkEditForm + from nautobot_chatops.integrations.grafana.models import Dashboard, Panel, PanelVariable diff --git a/nautobot_chatops/integrations/grafana/grafana.py b/nautobot_chatops/integrations/grafana/grafana.py index 36dd280a..f7e8dee5 100644 --- a/nautobot_chatops/integrations/grafana/grafana.py +++ b/nautobot_chatops/integrations/grafana/grafana.py @@ -1,15 +1,18 @@ """This module is intended to handle grafana requests generically perhaps outside of nautobot.""" + import datetime import logging import urllib.parse -from typing import Union, Tuple, List -import requests -import isodate +from http import HTTPStatus +from typing import List, Tuple, Union +import isodate +import requests from django.conf import settings from pydantic import BaseModel # pylint: disable=no-name-in-module from requests.exceptions import RequestException from typing_extensions import Literal + from nautobot_chatops.integrations.grafana.models import Panel, PanelVariable LOGGER = logging.getLogger("nautobot.plugin.grafana") @@ -203,7 +206,7 @@ def get_png(self, panel: Panel, panel_vars: List[PanelVariable]) -> Union[bytes, LOGGER.error("An error occurred while accessing the url: %s Exception: %s", url, exc) return None - if results.status_code == 200: + if results.status_code == HTTPStatus.OK: LOGGER.debug("Request returned %s", results.status_code) return results.content @@ -264,7 +267,7 @@ def get_dashboards(self) -> List[dict]: LOGGER.error("An error occurred while accessing the url: %s Exception: %s", url, exc) return [] - if results.status_code == 200: + if results.status_code == HTTPStatus.OK: LOGGER.debug("Request returned %s", results.status_code) return results.json() @@ -289,7 +292,7 @@ def get_panels(self, dashboard_uid: str) -> List[dict]: LOGGER.error("An error occurred while accessing the url: %s Exception: %s", url, exc) return [] - if results.status_code != 200: + if results.status_code != HTTPStatus.OK: LOGGER.error("Request returned %s for %s", results.status_code, url) return [] @@ -323,7 +326,7 @@ def get_variables(self, dashboard_uid: str) -> List[dict]: LOGGER.error("An error occurred while accessing the url: %s Exception: %s", url, exc) return [] - if results.status_code != 200: + if results.status_code != HTTPStatus.OK: LOGGER.error("Request returned %s for %s", results.status_code, url) return [] diff --git a/nautobot_chatops/integrations/grafana/helpers.py b/nautobot_chatops/integrations/grafana/helpers.py index 67e3f551..08d551f3 100644 --- a/nautobot_chatops/integrations/grafana/helpers.py +++ b/nautobot_chatops/integrations/grafana/helpers.py @@ -1,11 +1,12 @@ """Schema Enforcer wrapper used to mimic the validate cli functionality.""" + from typing import List -from termcolor import colored from schema_enforcer import config -from schema_enforcer.schemas.manager import SchemaManager -from schema_enforcer.instances.file import InstanceFileManager from schema_enforcer.exceptions import InvalidJSONSchema +from schema_enforcer.instances.file import InstanceFileManager +from schema_enforcer.schemas.manager import SchemaManager +from termcolor import colored SPECIAL_CHAR = { "%": "percent", diff --git a/nautobot_chatops/integrations/grafana/management/commands/gen_panels_from_dashboard.py b/nautobot_chatops/integrations/grafana/management/commands/gen_panels_from_dashboard.py index 1600cf6c..c3c7d573 100644 --- a/nautobot_chatops/integrations/grafana/management/commands/gen_panels_from_dashboard.py +++ b/nautobot_chatops/integrations/grafana/management/commands/gen_panels_from_dashboard.py @@ -1,9 +1,10 @@ """Generate a panels.yml file using the json file retrieved from Grafana.""" + import json -import yaml -from termcolor import colored +import yaml from django.core.management.base import BaseCommand +from termcolor import colored class Command(BaseCommand): diff --git a/nautobot_chatops/integrations/grafana/management/commands/import_panels.py b/nautobot_chatops/integrations/grafana/management/commands/import_panels.py index 77307798..ecbc7a2a 100644 --- a/nautobot_chatops/integrations/grafana/management/commands/import_panels.py +++ b/nautobot_chatops/integrations/grafana/management/commands/import_panels.py @@ -1,9 +1,10 @@ """Import a panels.yml file into the Grafana object models in Nautobot.""" -import yaml -from termcolor import colored +import yaml from django.core.management.base import BaseCommand from pydantic import ValidationError +from termcolor import colored + from nautobot_chatops.integrations.grafana.helpers import validate from nautobot_chatops.integrations.grafana.models import Dashboard, Panel, PanelVariable diff --git a/nautobot_chatops/integrations/grafana/management/commands/validate_schema.py b/nautobot_chatops/integrations/grafana/management/commands/validate_schema.py index 02ada2ef..e50080dd 100644 --- a/nautobot_chatops/integrations/grafana/management/commands/validate_schema.py +++ b/nautobot_chatops/integrations/grafana/management/commands/validate_schema.py @@ -1,5 +1,7 @@ """Nautobot Server CLI extension for Grafana ChatOps.""" + from django.core.management.base import BaseCommand + from nautobot_chatops.integrations.grafana.helpers import validate diff --git a/nautobot_chatops/integrations/grafana/models.py b/nautobot_chatops/integrations/grafana/models.py index c16f462e..63261ec1 100644 --- a/nautobot_chatops/integrations/grafana/models.py +++ b/nautobot_chatops/integrations/grafana/models.py @@ -1,10 +1,11 @@ """Models for Grafana Plugin.""" -from django.db import models + from django.core.exceptions import ValidationError from django.core.serializers.json import DjangoJSONEncoder +from django.db import models from django.utils.translation import gettext_lazy as _ from nautobot.circuits import models as circuit_models -from nautobot.core.models.generics import PrimaryModel, OrganizationalModel +from nautobot.core.models.generics import OrganizationalModel, PrimaryModel from nautobot.dcim import models as dcim_models from nautobot.extras import models as extra_models from nautobot.extras.utils import extras_features @@ -12,7 +13,6 @@ from nautobot.tenancy import models as tenancy_models from nautobot.virtualization import models as virtualization_models - # Valid models to be used in Panel Variables as query options. If a model doesn't exist in # this list, you cannot set or use the `query` field in a panel variable. VALID_MODELS = ( diff --git a/nautobot_chatops/integrations/grafana/navigation.py b/nautobot_chatops/integrations/grafana/navigation.py index bb757d2f..922fbf3a 100644 --- a/nautobot_chatops/integrations/grafana/navigation.py +++ b/nautobot_chatops/integrations/grafana/navigation.py @@ -1,4 +1,5 @@ """Navigation for Circuit Maintenance.""" + from nautobot.apps.ui import NavMenuAddButton, NavMenuItem items = [ diff --git a/nautobot_chatops/integrations/grafana/tables.py b/nautobot_chatops/integrations/grafana/tables.py index a8f9ec67..7234f848 100644 --- a/nautobot_chatops/integrations/grafana/tables.py +++ b/nautobot_chatops/integrations/grafana/tables.py @@ -1,8 +1,9 @@ """Django table classes for Nautobot.""" -from django_tables2 import TemplateColumn, Column, BooleanColumn -from nautobot.core.tables import BaseTable, ToggleColumn, ButtonsColumn -from nautobot_chatops.integrations.grafana.models import Panel, Dashboard, PanelVariable +from django_tables2 import BooleanColumn, Column, TemplateColumn +from nautobot.core.tables import BaseTable, ButtonsColumn, ToggleColumn + +from nautobot_chatops.integrations.grafana.models import Dashboard, Panel, PanelVariable class DashboardViewTable(BaseTable): diff --git a/nautobot_chatops/integrations/grafana/urls.py b/nautobot_chatops/integrations/grafana/urls.py index 1f1f7b25..65a44008 100644 --- a/nautobot_chatops/integrations/grafana/urls.py +++ b/nautobot_chatops/integrations/grafana/urls.py @@ -1,32 +1,34 @@ """Django urlpatterns declaration for nautobot_chatops.integrations.grafana app.""" + from django.urls import path from nautobot.extras.views import ObjectChangeLogView -from nautobot_chatops.integrations.grafana.models import Dashboard, PanelVariable, Panel + +from nautobot_chatops.integrations.grafana.models import Dashboard, Panel, PanelVariable from nautobot_chatops.integrations.grafana.views import ( + DashboardBulkEditView, Dashboards, + DashboardsBulkDeleteView, + DashboardsBulkImportView, DashboardsCreate, DashboardsDelete, DashboardsEdit, DashboardsSync, - DashboardsBulkImportView, - DashboardsBulkDeleteView, - DashboardBulkEditView, Panels, + PanelsBulkDeleteView, + PanelsBulkEditView, + PanelsBulkImportView, PanelsCreate, + PanelsDelete, PanelsEdit, PanelsSync, - PanelsDelete, - PanelsBulkImportView, - PanelsBulkDeleteView, - PanelsBulkEditView, Variables, - VariablesCreate, - VariablesSync, - VariablesEdit, - VariablesDelete, - VariablesBulkImportView, VariablesBulkDeleteView, VariablesBulkEditView, + VariablesBulkImportView, + VariablesCreate, + VariablesDelete, + VariablesEdit, + VariablesSync, ) urlpatterns = [ diff --git a/nautobot_chatops/integrations/grafana/views.py b/nautobot_chatops/integrations/grafana/views.py index 09fbc5bc..8ebeb6a1 100644 --- a/nautobot_chatops/integrations/grafana/views.py +++ b/nautobot_chatops/integrations/grafana/views.py @@ -4,36 +4,37 @@ to send requests and notifications to. """ -from django.shortcuts import render, reverse, redirect from django.contrib import messages from django.contrib.auth.mixins import PermissionRequiredMixin +from django.shortcuts import redirect, render, reverse +from nautobot.core.forms import ConfirmationForm from nautobot.core.views.generic import ( - ObjectEditView, - ObjectDeleteView, - ObjectListView, - BulkImportView, BulkDeleteView, BulkEditView, + BulkImportView, + ObjectDeleteView, + ObjectEditView, + ObjectListView, ) -from nautobot.core.forms import ConfirmationForm + from nautobot_chatops.integrations.grafana.diffsync.sync import run_dashboard_sync, run_panels_sync, run_variables_sync -from nautobot_chatops.integrations.grafana.tables import PanelViewTable, DashboardViewTable, PanelVariableViewTable -from nautobot_chatops.integrations.grafana.models import Panel, Dashboard, PanelVariable -from nautobot_chatops.integrations.grafana.grafana import handler from nautobot_chatops.integrations.grafana.filters import DashboardFilter, PanelFilter, VariableFilter from nautobot_chatops.integrations.grafana.forms import ( - DashboardsForm, - DashboardsFilterForm, DashboardBulkEditForm, + DashboardsFilterForm, + DashboardsForm, + PanelsBulkEditForm, + PanelsFilterForm, PanelsForm, PanelsSyncForm, - PanelsFilterForm, - PanelsBulkEditForm, + PanelVariablesBulkEditForm, + PanelVariablesFilterForm, PanelVariablesForm, PanelVariablesSyncForm, - PanelVariablesFilterForm, - PanelVariablesBulkEditForm, ) +from nautobot_chatops.integrations.grafana.grafana import handler +from nautobot_chatops.integrations.grafana.models import Dashboard, Panel, PanelVariable +from nautobot_chatops.integrations.grafana.tables import DashboardViewTable, PanelVariableViewTable, PanelViewTable # ------------------------------------------------------------------------------------- # Dashboard Specific Views diff --git a/nautobot_chatops/integrations/grafana/worker.py b/nautobot_chatops/integrations/grafana/worker.py index 0becc1d6..2901df44 100644 --- a/nautobot_chatops/integrations/grafana/worker.py +++ b/nautobot_chatops/integrations/grafana/worker.py @@ -1,26 +1,29 @@ """Worker function for /net commands in Slack.""" -import tempfile + import argparse import os +import tempfile from datetime import datetime -from typing import NoReturn, List, Union, Dict +from typing import Dict, List, NoReturn, Union + +from django.core.exceptions import FieldError, MultipleObjectsReturned, ObjectDoesNotExist from isodate import ISO8601Error, parse_duration from jinja2 import Template -from django.core.exceptions import FieldError, ObjectDoesNotExist, MultipleObjectsReturned -from pydantic.error_wrappers import ValidationError # pylint: disable=no-name-in-module from nautobot.core.models.querysets import RestrictedQuerySet +from pydantic.error_wrappers import ValidationError # pylint: disable=no-name-in-module + from nautobot_chatops.dispatchers import Dispatcher -from nautobot_chatops.workers import handle_subcommands, add_subcommand -from nautobot_chatops.integrations.grafana.models import Panel, PanelVariable, VALID_MODELS +from nautobot_chatops.integrations.grafana.exceptions import DefaultArgsError, MultipleOptionsError, PanelError from nautobot_chatops.integrations.grafana.grafana import ( - SLASH_COMMAND, - LOGGER, - GRAFANA_LOGO_PATH, GRAFANA_LOGO_ALT, + GRAFANA_LOGO_PATH, + LOGGER, REQUEST_TIMEOUT_SEC, + SLASH_COMMAND, handler, ) -from nautobot_chatops.integrations.grafana.exceptions import DefaultArgsError, PanelError, MultipleOptionsError +from nautobot_chatops.integrations.grafana.models import VALID_MODELS, Panel, PanelVariable +from nautobot_chatops.workers import add_subcommand, handle_subcommands def grafana_logo(dispatcher): @@ -68,6 +71,7 @@ def chat_get_panel(dispatcher: Dispatcher, *args) -> bool: # pylint: disable=to Args: dispatcher (nautobot_chatops.dispatchers.Dispatcher): Abstracted dispatcher class for chat-ops. + *args: Grafana Panel Arguments. Returns: bool: ChatOps response pass or fail. @@ -119,6 +123,7 @@ def chat_parse_args(panel_vars: List[PanelVariable], *args) -> Union[dict, bool] Args: panel_vars (List[nautobot_chatops.models.GrafanaPanelVariable]): List of PanelVariable objects. + *args: Grafana Panel arguments. Returns: parsed_args: dict of the arguments from the user's raw input diff --git a/nautobot_chatops/integrations/ipfabric/context.py b/nautobot_chatops/integrations/ipfabric/context.py index b8e2f38b..c99f715a 100644 --- a/nautobot_chatops/integrations/ipfabric/context.py +++ b/nautobot_chatops/integrations/ipfabric/context.py @@ -1,13 +1,17 @@ """Functions for caching per-user context.""" + import hashlib + from django.core.cache import cache + from nautobot_chatops import NautobotChatOpsConfig def _get_cache_key(user: str) -> str: """Key generator for the cache, adding the app prefix name.""" key_string = "-".join([NautobotChatOpsConfig.name, user]) - return hashlib.md5(key_string.encode("utf-8")).hexdigest() # nosec + # Since Hashlib is only used for caching, this does not pose a security risk. + return hashlib.md5(key_string.encode("utf-8")).hexdigest() # noqa: S324 def get_context(user: str) -> dict: diff --git a/nautobot_chatops/integrations/ipfabric/ipfabric_wrapper.py b/nautobot_chatops/integrations/ipfabric/ipfabric_wrapper.py index d8194dcd..f34a4c81 100644 --- a/nautobot_chatops/integrations/ipfabric/ipfabric_wrapper.py +++ b/nautobot_chatops/integrations/ipfabric/ipfabric_wrapper.py @@ -2,9 +2,8 @@ import logging -from ipfabric_diagrams import IPFDiagram from ipfabric import IPFClient - +from ipfabric_diagrams import IPFDiagram logger = logging.getLogger("nautobot") diff --git a/nautobot_chatops/integrations/ipfabric/worker.py b/nautobot_chatops/integrations/ipfabric/worker.py index 4957336d..59f3645a 100644 --- a/nautobot_chatops/integrations/ipfabric/worker.py +++ b/nautobot_chatops/integrations/ipfabric/worker.py @@ -1,7 +1,8 @@ """Worker functions implementing Nautobot "ipfabric" command and subcommands.""" # pylint: disable=too-many-lines + import logging -import tempfile import os +import tempfile from datetime import datetime from django.conf import settings @@ -11,11 +12,10 @@ from pkg_resources import parse_version from nautobot_chatops.choices import CommandStatusChoices -from nautobot_chatops.workers import subcommand_of, handle_subcommands - -from .ipfabric_wrapper import IpFabric +from nautobot_chatops.workers import handle_subcommands, subcommand_of from .context import get_context, set_context +from .ipfabric_wrapper import IpFabric from .utils import parse_hosts BASE_CMD = "ipfabric" @@ -429,9 +429,7 @@ def get_int_drops(dispatcher, device, snapshot_id): # PATH LOOKUP COMMMAND -def submit_pathlookup( - dispatcher, sub_cmd, src_ip, dst_ip, protocol, src_port=None, dst_port=None, icmp_type=None -): # pylint: disable=too-many-arguments, too-many-locals +def submit_pathlookup(dispatcher, sub_cmd, src_ip, dst_ip, protocol, src_port=None, dst_port=None, icmp_type=None): # pylint: disable=too-many-arguments, too-many-locals """Path simulation diagram lookup between source and target IP address.""" snapshot_id = get_user_snapshot(dispatcher) # diagrams for 4.0 - 4.2 are not supported due to attribute changes in 4.3+ @@ -486,9 +484,7 @@ def submit_pathlookup( @subcommand_of("ipfabric") -def pathlookup( - dispatcher, src_ip, dst_ip, src_port, dst_port, protocol -): # pylint: disable=too-many-arguments, too-many-locals +def pathlookup(dispatcher, src_ip, dst_ip, src_port, dst_port, protocol): # pylint: disable=too-many-arguments, too-many-locals """Path simulation diagram lookup between source and target IP address.""" sub_cmd = "pathlookup" supported_protocols = ["tcp", "udp"] @@ -976,9 +972,7 @@ def find_host(dispatcher, filter_key=None, filter_value=None): @subcommand_of("ipfabric") -def table_diff( - dispatcher, category, table, view, snapshot -): # pylint: disable=too-many-return-statements, too-many-branches +def table_diff(dispatcher, category, table, view, snapshot): # pylint: disable=too-many-return-statements, too-many-branches """Get difference of a table between the current snapshot and the specified snapshot.""" sub_cmd = "table-diff" diff --git a/nautobot_chatops/integrations/meraki/worker.py b/nautobot_chatops/integrations/meraki/worker.py index 6d08f2c2..6c2133b8 100644 --- a/nautobot_chatops/integrations/meraki/worker.py +++ b/nautobot_chatops/integrations/meraki/worker.py @@ -1,14 +1,15 @@ """Demo meraki addition to Nautobot.""" -import os + import logging +import os from django.conf import settings -from nautobot_chatops.workers import subcommand_of, handle_subcommands + from nautobot_chatops.choices import CommandStatusChoices +from nautobot_chatops.workers import handle_subcommands, subcommand_of from .utils import MerakiClient - MERAKI_LOGO_PATH = "nautobot_meraki/meraki.png" MERAKI_LOGO_ALT = "Meraki Logo" diff --git a/nautobot_chatops/integrations/nso/jinja_filters.py b/nautobot_chatops/integrations/nso/jinja_filters.py index 50688bf0..cf8cbfb5 100644 --- a/nautobot_chatops/integrations/nso/jinja_filters.py +++ b/nautobot_chatops/integrations/nso/jinja_filters.py @@ -1,5 +1,7 @@ """Custom filters for nautobot_chatops.integrations.nso.""" + from django_jinja import library + from nautobot_chatops.integrations.nso.nso import NSOClient diff --git a/nautobot_chatops/integrations/nso/nso.py b/nautobot_chatops/integrations/nso/nso.py index a4a9b77c..d67b4556 100644 --- a/nautobot_chatops/integrations/nso/nso.py +++ b/nautobot_chatops/integrations/nso/nso.py @@ -1,18 +1,18 @@ """All interactions with nso.""" import logging + import requests from django.conf import settings from rest_framework import status from nautobot_chatops.integrations.nso.exceptions import ( CommunicationError, + DeviceLocked, DeviceNotFound, DeviceNotSupported, - DeviceLocked, ) - logger = logging.getLogger("nautobot.plugin.nso") # Import config vars from nautobot_config.py diff --git a/nautobot_chatops/integrations/nso/worker.py b/nautobot_chatops/integrations/nso/worker.py index 2da09bb2..291afd76 100644 --- a/nautobot_chatops/integrations/nso/worker.py +++ b/nautobot_chatops/integrations/nso/worker.py @@ -1,11 +1,12 @@ """Worker functions implementing Nautobot "nso" command and subcommands.""" + from django.core.exceptions import ObjectDoesNotExist -from nautobot.dcim.models import Device from nautobot.core.settings_funcs import is_truthy +from nautobot.dcim.models import Device from nautobot_chatops.choices import CommandStatusChoices +from nautobot_chatops.integrations.nso.nso import REQUEST_TIMEOUT_SEC, SLASH_COMMAND, NSOClient from nautobot_chatops.workers import handle_subcommands, subcommand_of -from nautobot_chatops.integrations.nso.nso import NSOClient, REQUEST_TIMEOUT_SEC, SLASH_COMMAND def nso_logo(dispatcher): diff --git a/nautobot_chatops/integrations/panorama/constant.py b/nautobot_chatops/integrations/panorama/constant.py index 1748f8f1..aa269da7 100644 --- a/nautobot_chatops/integrations/panorama/constant.py +++ b/nautobot_chatops/integrations/panorama/constant.py @@ -1,4 +1,5 @@ """Storage of data that will not change throughout the life cycle of application.""" + from django.conf import settings PLUGIN_CFG = settings.PLUGINS_CONFIG["nautobot_chatops"] diff --git a/nautobot_chatops/integrations/panorama/jinja_filters.py b/nautobot_chatops/integrations/panorama/jinja_filters.py index ac7798c9..c3d0df3f 100644 --- a/nautobot_chatops/integrations/panorama/jinja_filters.py +++ b/nautobot_chatops/integrations/panorama/jinja_filters.py @@ -1,4 +1,5 @@ """Custom Django Jinja filters.""" + from django_jinja import library diff --git a/nautobot_chatops/integrations/panorama/utils.py b/nautobot_chatops/integrations/panorama/utils.py index 4d82f8b3..a5aba962 100644 --- a/nautobot_chatops/integrations/panorama/utils.py +++ b/nautobot_chatops/integrations/panorama/utils.py @@ -1,20 +1,20 @@ """Functions used for interacting with Panroama.""" + import logging import time from typing import List + import defusedxml.ElementTree as ET import requests - from netmiko import ConnectHandler from panos.errors import PanDeviceXapiError from panos.firewall import Firewall -from panos.panorama import Panorama, DeviceGroup, PanoramaDeviceGroupHierarchy +from panos.panorama import DeviceGroup, Panorama, PanoramaDeviceGroupHierarchy from panos.policies import PostRulebase, PreRulebase, Rulebase, SecurityRule from requests.exceptions import RequestException from .constant import DEFAULT_TIMEOUT, PLUGIN_CFG - logger = logging.getLogger(__name__) @@ -31,10 +31,11 @@ def get_api_key_api(url: str = PLUGIN_CFG["panorama_host"]) -> str: params = {"type": "keygen", "user": PLUGIN_CFG["panorama_user"], "password": PLUGIN_CFG["panorama_password"]} + # TODO: The Verify option should be configurable. response = requests.get( f"https://{url}/api/", params=params, - verify=False, # nosec + verify=False, # noqa: S501 timeout=DEFAULT_TIMEOUT, ) if response.status_code != 200: @@ -82,6 +83,8 @@ def get_from_pano(connection: Panorama, devices: bool = False, groups: bool = Fa Args: connection (Panorama): Connection object to Panorama. + devices (bool): Get Devices from Panorama. + groups (bool): Get Groups from Panorama. Returns: dict: Dictionary of all devices attached to Panorama. @@ -181,12 +184,12 @@ def start_packet_capture(capture_filename: str, ip_address: str, filters: dict): if filters["dport"] and filters["dport"] != "any": command += f" destination-port {filters['dport']}" - if filters["dnet"] != "0.0.0.0": # nosec + if filters["dnet"] != "0.0.0.0": # noqa: S104 command += f" destination {filters['dnet']}" if filters["dcidr"] != "0": command += f" destination-netmask {filters['dcidr']}" - if filters["snet"] != "0.0.0.0": # nosec + if filters["snet"] != "0.0.0.0": # noqa: S104 command += f" source {filters['snet']}" if filters["scidr"] != "0": command += f" source-netmask {filters['scidr']}" @@ -223,10 +226,11 @@ def _get_pcap(capture_filename: str, ip_address: str): params = {"key": get_api_key_api(), "type": "export", "category": "filters-pcap", "from": "1.pcap"} + # TODO: The Verify option should be configurable. respone = requests.get( url, params=params, - verify=False, # nosec + verify=False, # noqa S501 # nosec timeout=DEFAULT_TIMEOUT, ) diff --git a/nautobot_chatops/integrations/panorama/worker.py b/nautobot_chatops/integrations/panorama/worker.py index 799dfa60..879ef69e 100644 --- a/nautobot_chatops/integrations/panorama/worker.py +++ b/nautobot_chatops/integrations/panorama/worker.py @@ -1,4 +1,5 @@ """Example worker to handle /panorama chat commands with 1 subcommand addition.""" + import logging import os import re @@ -15,13 +16,13 @@ from nautobot_chatops.choices import CommandStatusChoices from nautobot_chatops.integrations.panorama.utils import ( connect_panorama, - get_from_pano, - get_rule_match, - start_packet_capture, get_all_rules, - split_rules, + get_from_pano, get_object, get_panorama_device_group_hierarchy, + get_rule_match, + split_rules, + start_packet_capture, ) from nautobot_chatops.workers import handle_subcommands, subcommand_of @@ -174,9 +175,7 @@ def get_devicegroups(dispatcher, **kwargs): @subcommand_of("panorama") -def validate_rule_exists( - dispatcher, device, src_ip, dst_ip, protocol, dst_port -): # pylint:disable=too-many-arguments,too-many-locals,too-many-branches,too-many-statements +def validate_rule_exists(dispatcher, device, src_ip, dst_ip, protocol, dst_port): # pylint:disable=too-many-arguments,too-many-locals,too-many-branches,too-many-statements """Verify that the rule exists within a device, via Panorama.""" dialog_list = [ { @@ -574,6 +573,7 @@ def capture_traffic( ip_proto (str): Protocol for destination port stage (str): Stage to use capture_seconds (str): Number of seconds to run packet capture + **kwargs: Any additional args """ logger.info("Starting capture_traffic()") diff --git a/nautobot_chatops/integrations/utils.py b/nautobot_chatops/integrations/utils.py index 0ec22c93..32b49c21 100644 --- a/nautobot_chatops/integrations/utils.py +++ b/nautobot_chatops/integrations/utils.py @@ -1,4 +1,5 @@ """Utility functions for nautobot_chatops integrations.""" + from importlib import import_module from pathlib import Path from types import ModuleType diff --git a/nautobot_chatops/management/commands/start_slack_socket.py b/nautobot_chatops/management/commands/start_slack_socket.py index 0c8fb3f2..5a029488 100644 --- a/nautobot_chatops/management/commands/start_slack_socket.py +++ b/nautobot_chatops/management/commands/start_slack_socket.py @@ -1,7 +1,9 @@ """Command to start a slack socket.""" import asyncio + from django.core.management.base import BaseCommand + from nautobot_chatops.sockets.slack import main diff --git a/nautobot_chatops/migrations/0001_initial.py b/nautobot_chatops/migrations/0001_initial.py index e2d836c3..e6b79eee 100644 --- a/nautobot_chatops/migrations/0001_initial.py +++ b/nautobot_chatops/migrations/0001_initial.py @@ -1,9 +1,10 @@ # Generated by Django 3.1.3 on 2021-02-22 02:18 +import uuid + import django.contrib.postgres.fields -from django.db import migrations, models, connection import nautobot.core.models.fields -import uuid +from django.db import connection, migrations, models class Migration(migrations.Migration): diff --git a/nautobot_chatops/migrations/0002_commandlog_params1.py b/nautobot_chatops/migrations/0002_commandlog_params1.py index 9e8789ed..ae7e5fbd 100644 --- a/nautobot_chatops/migrations/0002_commandlog_params1.py +++ b/nautobot_chatops/migrations/0002_commandlog_params1.py @@ -1,7 +1,6 @@ # Generated by Django 3.1.12 on 2021-08-14 02:29 from django.db import migrations, models -import nautobot_chatops.models class Migration(migrations.Migration): diff --git a/nautobot_chatops/migrations/0003_params_to_params1.py b/nautobot_chatops/migrations/0003_params_to_params1.py index aae448b0..5001056d 100644 --- a/nautobot_chatops/migrations/0003_params_to_params1.py +++ b/nautobot_chatops/migrations/0003_params_to_params1.py @@ -1,7 +1,6 @@ import copy -import json -from django.db import migrations, connection +from django.db import connection, migrations def migrate_params(apps, schema_editor): diff --git a/nautobot_chatops/migrations/0004_remove_params_rename_params1.py b/nautobot_chatops/migrations/0004_remove_params_rename_params1.py index 6d9d36a8..f8e2d17e 100644 --- a/nautobot_chatops/migrations/0004_remove_params_rename_params1.py +++ b/nautobot_chatops/migrations/0004_remove_params_rename_params1.py @@ -1,4 +1,4 @@ -from django.db import migrations, connection +from django.db import connection, migrations class Migration(migrations.Migration): diff --git a/nautobot_chatops/migrations/0005_grafana.py b/nautobot_chatops/migrations/0005_grafana.py index 96479648..db389724 100644 --- a/nautobot_chatops/migrations/0005_grafana.py +++ b/nautobot_chatops/migrations/0005_grafana.py @@ -1,13 +1,14 @@ # Generated by Django 3.2.15 on 2023-05-19 11:57 +import uuid + import django.core.serializers.json -from django.db import migrations, models import django.db.models.deletion import nautobot.extras.models.mixins import taggit.managers -import uuid -from django.db.migrations.recorder import MigrationRecorder from django.contrib.contenttypes.models import ContentType +from django.db import migrations, models +from django.db.migrations.recorder import MigrationRecorder _APP_LABEL = "nautobot_chatops" _OLD_APP_LABEL = "nautobot_plugin_chatops_grafana" @@ -22,7 +23,7 @@ def _copy(model_name, apps, connection): with connection.cursor() as cursor: # Table names are from trusted source (this script) - cursor.execute(f"INSERT INTO {new_table_name} SELECT * FROM {old_table_name};") # nosec + cursor.execute(f"INSERT INTO {new_table_name} SELECT * FROM {old_table_name};") # noqa: S608 # Update the content type to point to the new model old_content_type = ContentType.objects.get(app_label=_OLD_APP_LABEL, model=model_name.lower()) diff --git a/nautobot_chatops/migrations/0006_nautobot_2.py b/nautobot_chatops/migrations/0006_nautobot_2.py index ce19a15c..fd253bcc 100644 --- a/nautobot_chatops/migrations/0006_nautobot_2.py +++ b/nautobot_chatops/migrations/0006_nautobot_2.py @@ -1,8 +1,8 @@ # Generated by Django 3.2.20 on 2023-08-25 16:40 import django.core.serializers.json -from django.db import migrations, models import nautobot.core.models.fields +from django.db import migrations, models class Migration(migrations.Migration): diff --git a/nautobot_chatops/migrations/0007_account_link.py b/nautobot_chatops/migrations/0007_account_link.py index ca7f06bd..f3f5e380 100644 --- a/nautobot_chatops/migrations/0007_account_link.py +++ b/nautobot_chatops/migrations/0007_account_link.py @@ -1,12 +1,13 @@ # Generated by Django 3.2.20 on 2023-09-14 17:34 -from django.conf import settings +import uuid + import django.core.serializers.json -from django.db import migrations, models import django.db.models.deletion import nautobot.core.models.fields import nautobot.extras.models.mixins -import uuid +from django.conf import settings +from django.db import migrations, models class Migration(migrations.Migration): diff --git a/nautobot_chatops/models.py b/nautobot_chatops/models.py index b6b557b9..d9d29ffc 100644 --- a/nautobot_chatops/models.py +++ b/nautobot_chatops/models.py @@ -4,30 +4,28 @@ from django.core.exceptions import ValidationError from django.db import models from django.urls import reverse - from nautobot.core.models.fields import ColorField from nautobot.core.models.generics import PrimaryModel -from .choices import AccessGrantTypeChoices, CommandStatusChoices, PlatformChoices - -from .integrations.grafana.models import Dashboard as GrafanaDashboard -from .integrations.grafana.models import Panel as GrafanaPanel -from .integrations.grafana.models import PanelVariable as GrafanaPanelVariable +from .choices import AccessGrantTypeChoices, CommandStatusChoices, PlatformChoices from .constants import ( - COMMAND_LOG_USER_NAME_HELP_TEXT, - COMMAND_LOG_USER_ID_HELP_TEXT, - COMMAND_LOG_PLATFORM_HELP_TEXT, - COMMAND_LOG_COMMAND_TEXT, - COMMAND_LOG_SUBCOMMAND_HELP_TEXT, - COMMAND_LOG_PARAMS_HELP_TEXT, ACCESS_GRANT_COMMAND_HELP_TEXT, - ACCESS_GRANT_SUBCOMMAND_HELP_TEXT, ACCESS_GRANT_NAME_HELP_TEXT, + ACCESS_GRANT_SUBCOMMAND_HELP_TEXT, ACCESS_GRANT_VALUE_HELP_TEXT, + CHATOPS_USER_ID_HELP_TEXT, + COMMAND_LOG_COMMAND_TEXT, + COMMAND_LOG_PARAMS_HELP_TEXT, + COMMAND_LOG_PLATFORM_HELP_TEXT, + COMMAND_LOG_SUBCOMMAND_HELP_TEXT, + COMMAND_LOG_USER_ID_HELP_TEXT, + COMMAND_LOG_USER_NAME_HELP_TEXT, COMMAND_TOKEN_COMMENT_HELP_TEXT, COMMAND_TOKEN_TOKEN_HELP_TEXT, - CHATOPS_USER_ID_HELP_TEXT, ) +from .integrations.grafana.models import Dashboard as GrafanaDashboard +from .integrations.grafana.models import Panel as GrafanaPanel +from .integrations.grafana.models import PanelVariable as GrafanaPanelVariable class CommandLog(PrimaryModel): # pylint: disable=nb-string-field-blank-null diff --git a/nautobot_chatops/sockets/slack.py b/nautobot_chatops/sockets/slack.py index 6d8dd1bb..e4482db7 100644 --- a/nautobot_chatops/sockets/slack.py +++ b/nautobot_chatops/sockets/slack.py @@ -6,13 +6,13 @@ from django.conf import settings from slack_sdk.socket_mode.aiohttp import SocketModeClient -from slack_sdk.socket_mode.response import SocketModeResponse from slack_sdk.socket_mode.request import SocketModeRequest +from slack_sdk.socket_mode.response import SocketModeResponse from slack_sdk.web.async_client import AsyncWebClient -from nautobot_chatops.workers import get_commands_registry, commands_help, parse_command_string from nautobot_chatops.dispatchers.slack import SlackDispatcher from nautobot_chatops.utils import socket_check_and_enqueue_command +from nautobot_chatops.workers import commands_help, get_commands_registry, parse_command_string # pylint: disable-next=too-many-statements diff --git a/nautobot_chatops/tables.py b/nautobot_chatops/tables.py index 18c20097..efdcd0fa 100644 --- a/nautobot_chatops/tables.py +++ b/nautobot_chatops/tables.py @@ -1,10 +1,9 @@ """Django table classes for Nautobot.""" -from django_tables2 import TemplateColumn, LinkColumn - +from django_tables2 import LinkColumn, TemplateColumn from nautobot.core.tables import BaseTable, ButtonsColumn, ToggleColumn -from .models import CommandLog, AccessGrant, CommandToken, ChatOpsAccountLink +from .models import AccessGrant, ChatOpsAccountLink, CommandLog, CommandToken class CommandLogTable(BaseTable): diff --git a/nautobot_chatops/tests/aci/test_aci.py b/nautobot_chatops/tests/aci/test_aci.py index 1385399a..2f1dfb12 100644 --- a/nautobot_chatops/tests/aci/test_aci.py +++ b/nautobot_chatops/tests/aci/test_aci.py @@ -1,7 +1,9 @@ """Tests for integrations.aci.aci.""" + # pylint: disable=invalid-name import unittest -from unittest.mock import patch, Mock +from unittest.mock import Mock, patch + from nautobot_chatops.integrations.aci.aci import NautobotPluginChatopsAci, RequestHTTPError @@ -312,9 +314,7 @@ def test_get_static_path_negative(self, mocked_login, mocked_handle_request): mock_response.ok = False mocked_login.return_value = self.mock_login mocked_handle_request.return_value = mock_response - self.assertRaises( - RequestHTTPError, self.aci_obj.get_static_path, "test-tenant-1", "test-ap", "test-epg" - ) # nosec + self.assertRaises(RequestHTTPError, self.aci_obj.get_static_path, "test-tenant-1", "test-ap", "test-epg") # nosec @patch.object(NautobotPluginChatopsAci, "get_static_path") @patch.object(NautobotPluginChatopsAci, "get_contract_filters") diff --git a/nautobot_chatops/tests/ansible/test_tower.py b/nautobot_chatops/tests/ansible/test_tower.py index f8c28f0a..a28aeeb9 100644 --- a/nautobot_chatops/tests/ansible/test_tower.py +++ b/nautobot_chatops/tests/ansible/test_tower.py @@ -1,4 +1,5 @@ """Test of tower.py.""" + from collections import namedtuple from os import path diff --git a/nautobot_chatops/tests/aristacv/test_api.py b/nautobot_chatops/tests/aristacv/test_api.py index a09083d2..4b58e0c2 100644 --- a/nautobot_chatops/tests/aristacv/test_api.py +++ b/nautobot_chatops/tests/aristacv/test_api.py @@ -1,12 +1,12 @@ """Unit tests for Arista CloudVision integration API.""" + from django.contrib.auth import get_user_model from django.test import TestCase from django.urls import reverse +from nautobot.users.models import Token from rest_framework import status from rest_framework.test import APIClient -from nautobot.users.models import Token - User = get_user_model() diff --git a/nautobot_chatops/tests/grafana/test_api.py b/nautobot_chatops/tests/grafana/test_api.py index 759571a0..4a5af59c 100644 --- a/nautobot_chatops/tests/grafana/test_api.py +++ b/nautobot_chatops/tests/grafana/test_api.py @@ -1,12 +1,12 @@ """Unit tests for nautobot_chatops.integrations.grafana.""" + from django.contrib.auth import get_user_model from django.test import TestCase from django.urls import reverse +from nautobot.users.models import Token from rest_framework import status from rest_framework.test import APIClient -from nautobot.users.models import Token - User = get_user_model() diff --git a/nautobot_chatops/tests/grafana/test_workers.py b/nautobot_chatops/tests/grafana/test_workers.py index 2707c037..1deef81a 100644 --- a/nautobot_chatops/tests/grafana/test_workers.py +++ b/nautobot_chatops/tests/grafana/test_workers.py @@ -1,13 +1,13 @@ """Test cases for the Nautobot workers module.""" -from django.test import TestCase +from django.test import TestCase from prybar import dynamic_entrypoint -from nautobot_chatops.workers import parse_command_string, get_commands_registry, add_subcommand -from nautobot_chatops.tests.workers.dynamic_commands import dynamic_command, dynamic_subcommand import nautobot_chatops.workers +from nautobot_chatops.integrations.grafana.models import Dashboard, Panel from nautobot_chatops.integrations.grafana.worker import initialize_subcommands -from nautobot_chatops.integrations.grafana.models import Panel, Dashboard +from nautobot_chatops.tests.workers.dynamic_commands import dynamic_command, dynamic_subcommand +from nautobot_chatops.workers import add_subcommand, get_commands_registry, parse_command_string class TestGrafana(TestCase): diff --git a/nautobot_chatops/tests/ipfabric/test_api.py b/nautobot_chatops/tests/ipfabric/test_api.py index d6638d9d..ca5b63ad 100644 --- a/nautobot_chatops/tests/ipfabric/test_api.py +++ b/nautobot_chatops/tests/ipfabric/test_api.py @@ -1,12 +1,12 @@ """Unit tests for IP Fabric Integration.""" + from django.contrib.auth import get_user_model from django.test import TestCase from django.urls import reverse +from nautobot.users.models import Token from rest_framework import status from rest_framework.test import APIClient -from nautobot.users.models import Token - User = get_user_model() diff --git a/nautobot_chatops/tests/meraki/test_utils.py b/nautobot_chatops/tests/meraki/test_utils.py index 3a14a80b..32409eed 100644 --- a/nautobot_chatops/tests/meraki/test_utils.py +++ b/nautobot_chatops/tests/meraki/test_utils.py @@ -1,4 +1,5 @@ """Test of utils.py.""" + import unittest from unittest.mock import patch diff --git a/nautobot_chatops/tests/nso/test_nso.py b/nautobot_chatops/tests/nso/test_nso.py index 995a787f..08c89d09 100644 --- a/nautobot_chatops/tests/nso/test_nso.py +++ b/nautobot_chatops/tests/nso/test_nso.py @@ -1,19 +1,19 @@ """Test rundeck client.""" # Disable protected access, since..ya know. We need to test them. # pylint: disable=protected-access -from os import path import json +from os import path + import responses from django.test import SimpleTestCase -from nautobot_chatops.integrations.nso.nso import NSOClient as nso from nautobot_chatops.integrations.nso.exceptions import ( CommunicationError, + DeviceLocked, DeviceNotFound, DeviceNotSupported, - DeviceLocked, ) - +from nautobot_chatops.integrations.nso.nso import NSOClient as nso HERE = path.abspath(path.dirname(__file__)) diff --git a/nautobot_chatops/tests/panorama/test_utils_panorama.py b/nautobot_chatops/tests/panorama/test_utils_panorama.py index 87416272..f68b8358 100644 --- a/nautobot_chatops/tests/panorama/test_utils_panorama.py +++ b/nautobot_chatops/tests/panorama/test_utils_panorama.py @@ -1,6 +1,9 @@ """Unit tests for Panorama utility functions.""" + from unittest.mock import MagicMock + from nautobot.core.testing import TestCase + from nautobot_chatops.integrations.panorama.utils import get_from_pano diff --git a/nautobot_chatops/tests/test_api.py b/nautobot_chatops/tests/test_api.py index 3113ce4e..f79f6294 100644 --- a/nautobot_chatops/tests/test_api.py +++ b/nautobot_chatops/tests/test_api.py @@ -1,11 +1,15 @@ -"""Test cases for Nautobot Chatops API.""" +"""Unit tests for nautobot_chatops.""" + from importlib import metadata +from django.contrib.auth import get_user_model from django.urls import reverse - from nautobot.core.testing import APITestCase, APIViewTestCases + from nautobot_chatops.models import AccessGrant, CommandToken +User = get_user_model() + nautobot_version = metadata.version("nautobot") diff --git a/nautobot_chatops/tests/test_basic.py b/nautobot_chatops/tests/test_basic.py index bcf7d12d..d72f2d02 100644 --- a/nautobot_chatops/tests/test_basic.py +++ b/nautobot_chatops/tests/test_basic.py @@ -1,6 +1,8 @@ """Basic tests that do not require Django.""" -import unittest + 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_chatops/tests/test_dispatchers.py b/nautobot_chatops/tests/test_dispatchers.py index efea9bfb..d4c032ea 100644 --- a/nautobot_chatops/tests/test_dispatchers.py +++ b/nautobot_chatops/tests/test_dispatchers.py @@ -1,15 +1,15 @@ """Tests for Nautobot dispatcher class implementations.""" -from unittest.mock import patch, MagicMock + +from unittest.mock import MagicMock, patch from django.conf import settings from django.test import TestCase from slack_sdk.errors import SlackApiError +from nautobot_chatops.dispatchers.mattermost import MattermostDispatcher from nautobot_chatops.dispatchers.ms_teams import MSTeamsDispatcher from nautobot_chatops.dispatchers.slack import SlackDispatcher from nautobot_chatops.dispatchers.webex import WebexDispatcher -from nautobot_chatops.dispatchers.mattermost import MattermostDispatcher - # pylint: disable=unnecessary-pass diff --git a/nautobot_chatops/tests/test_utils.py b/nautobot_chatops/tests/test_utils.py index 8d5276e2..66c592f7 100644 --- a/nautobot_chatops/tests/test_utils.py +++ b/nautobot_chatops/tests/test_utils.py @@ -9,7 +9,6 @@ from nautobot_chatops.models import AccessGrant from nautobot_chatops.utils import check_and_enqueue_command - User = get_user_model() diff --git a/nautobot_chatops/tests/test_views.py b/nautobot_chatops/tests/test_views.py index 2297990a..8db45e02 100644 --- a/nautobot_chatops/tests/test_views.py +++ b/nautobot_chatops/tests/test_views.py @@ -5,21 +5,23 @@ from django.conf import settings from django.test import TestCase from django.test.client import RequestFactory - from nautobot.core.testing import ViewTestCases + +from nautobot_chatops.api.views.mattermost import verify_signature as mattermost_verify_signature from nautobot_chatops.api.views.slack import ( - verify_signature as slack_verify_signature, generate_signature as slack_generate_signature, ) +from nautobot_chatops.api.views.slack import ( + verify_signature as slack_verify_signature, +) from nautobot_chatops.api.views.webex import ( - verify_signature as webex_verify_signature, generate_signature as webex_generate_signature, ) -from nautobot_chatops.api.views.mattermost import verify_signature as mattermost_verify_signature -from nautobot_chatops.choices import PlatformChoices -from nautobot_chatops.models import CommandToken -from nautobot_chatops.choices import CommandStatusChoices -from nautobot_chatops.models import CommandLog +from nautobot_chatops.api.views.webex import ( + verify_signature as webex_verify_signature, +) +from nautobot_chatops.choices import CommandStatusChoices, PlatformChoices +from nautobot_chatops.models import CommandLog, CommandToken class TestSignatureVerification(TestCase): diff --git a/nautobot_chatops/tests/test_workers.py b/nautobot_chatops/tests/test_workers.py index 65120bfb..56bae1f7 100644 --- a/nautobot_chatops/tests/test_workers.py +++ b/nautobot_chatops/tests/test_workers.py @@ -1,11 +1,11 @@ """Test cases for the Nautobot workers module.""" -from django.test import SimpleTestCase +from django.test import SimpleTestCase from prybar import dynamic_entrypoint -from nautobot_chatops.workers import convert_smart_quotes, parse_command_string, get_commands_registry, add_subcommand -from nautobot_chatops.tests.workers.dynamic_commands import dynamic_command, dynamic_subcommand import nautobot_chatops.workers +from nautobot_chatops.tests.workers.dynamic_commands import dynamic_command, dynamic_subcommand +from nautobot_chatops.workers import add_subcommand, convert_smart_quotes, get_commands_registry, parse_command_string class TestFunctions(SimpleTestCase): @@ -128,11 +128,11 @@ def test_convert_smart_quotes(self): """Verify Convert Smart Quotes.""" self.assertEqual(convert_smart_quotes("''"), "''") self.assertEqual(convert_smart_quotes(""), "") - self.assertEqual(convert_smart_quotes("\u201C\u201D"), "''") + self.assertEqual(convert_smart_quotes("\u201c\u201d"), "''") self.assertEqual(convert_smart_quotes("\u2018\u2019"), "''") - self.assertEqual(convert_smart_quotes("\u201C"), "'") - self.assertEqual(convert_smart_quotes("\u201D"), "'") + self.assertEqual(convert_smart_quotes("\u201c"), "'") + self.assertEqual(convert_smart_quotes("\u201d"), "'") self.assertEqual(convert_smart_quotes("\u2018"), "'") self.assertEqual(convert_smart_quotes("\u2019"), "'") - self.assertEqual(convert_smart_quotes("\u201CLas Vegas\u201D"), "'Las Vegas'") + self.assertEqual(convert_smart_quotes("\u201cLas Vegas\u201d"), "'Las Vegas'") self.assertEqual(convert_smart_quotes("\u2018Las Vegas\u2019"), "'Las Vegas'") diff --git a/nautobot_chatops/tests/workers/dynamic_commands.py b/nautobot_chatops/tests/workers/dynamic_commands.py index e2e286fc..433b046f 100644 --- a/nautobot_chatops/tests/workers/dynamic_commands.py +++ b/nautobot_chatops/tests/workers/dynamic_commands.py @@ -1,6 +1,6 @@ """Test file defining a dynamic subcommand (issue #54).""" -from nautobot_chatops.workers import subcommand_of, handle_subcommands +from nautobot_chatops.workers import handle_subcommands, subcommand_of def dynamic_command(subcommand, **kwargs): diff --git a/nautobot_chatops/tests/workers/test_nautobot.py b/nautobot_chatops/tests/workers/test_nautobot.py index f215a917..05e0cd40 100644 --- a/nautobot_chatops/tests/workers/test_nautobot.py +++ b/nautobot_chatops/tests/workers/test_nautobot.py @@ -1,13 +1,14 @@ """Tests for the /nautobot chatops commands.""" + from unittest.mock import MagicMock from django.contrib.contenttypes.models import ContentType from django.test import TestCase from nautobot.dcim.models import Location, LocationType -from nautobot.ipam.models import VLAN from nautobot.extras.models import Status -from nautobot_chatops.choices import CommandStatusChoices +from nautobot.ipam.models import VLAN +from nautobot_chatops.choices import CommandStatusChoices from nautobot_chatops.dispatchers import Dispatcher from nautobot_chatops.workers.nautobot import get_vlans diff --git a/nautobot_chatops/tests/workers/two_commands.py b/nautobot_chatops/tests/workers/two_commands.py index 7d08e147..1e3b7e16 100644 --- a/nautobot_chatops/tests/workers/two_commands.py +++ b/nautobot_chatops/tests/workers/two_commands.py @@ -1,6 +1,6 @@ """Test file defining two commands and their subcommands (issue #20).""" -from nautobot_chatops.workers import subcommand_of, handle_subcommands +from nautobot_chatops.workers import handle_subcommands, subcommand_of def first_command(subcommand, **kwargs): diff --git a/nautobot_chatops/urls.py b/nautobot_chatops/urls.py index 53d9db14..a3e5a559 100644 --- a/nautobot_chatops/urls.py +++ b/nautobot_chatops/urls.py @@ -1,28 +1,28 @@ """Django urlpatterns declaration for nautobot_chatops app.""" + import logging -from django.urls import path from django.templatetags.static import static +from django.urls import path from django.views.generic import RedirectView - from nautobot.apps.config import get_app_settings_or_config from nautobot.extras.views import ObjectChangeLogView, ObjectNotesView from nautobot_chatops.models import AccessGrant, ChatOpsAccountLink, CommandLog, CommandToken from nautobot_chatops.views import ( - CommandTokenBulkDeleteView, - CommandTokenCreateView, - CommandTokenListView, - CommandTokenView, - CommandLogListView, + AccessGrantBulkDeleteView, + AccessGrantCreateView, AccessGrantListView, AccessGrantView, - AccessGrantCreateView, - AccessGrantBulkDeleteView, - ChatOpsAccountLinkView, + ChatOpsAccountLinkDeleteView, ChatOpsAccountLinkEditView, ChatOpsAccountLinkListView, - ChatOpsAccountLinkDeleteView, + ChatOpsAccountLinkView, + CommandLogListView, + CommandTokenBulkDeleteView, + CommandTokenCreateView, + CommandTokenListView, + CommandTokenView, ) if get_app_settings_or_config("nautobot_chatops", "enable_grafana"): diff --git a/nautobot_chatops/utils.py b/nautobot_chatops/utils.py index 4ad812f0..19d99915 100644 --- a/nautobot_chatops/utils.py +++ b/nautobot_chatops/utils.py @@ -1,8 +1,8 @@ """Utility functions for API view implementations.""" -from datetime import datetime, timezone import logging import sys +from datetime import datetime, timezone from asgiref.sync import sync_to_async from django.conf import settings @@ -11,8 +11,8 @@ from nautobot.core.celery import nautobot_task from nautobot_chatops.choices import AccessGrantTypeChoices, CommandStatusChoices -from nautobot_chatops.models import AccessGrant, CommandLog from nautobot_chatops.metrics import request_command_cntr +from nautobot_chatops.models import AccessGrant, CommandLog logger = logging.getLogger(__name__) @@ -118,9 +118,7 @@ def socket_check_and_enqueue_command(*args, **kwargs): return check_and_enqueue_command(*args, **kwargs) -def check_and_enqueue_command( - registry, command, subcommand, params, context, dispatcher_class -): # pylint:disable=too-many-statements +def check_and_enqueue_command(registry, command, subcommand, params, context, dispatcher_class): # noqa: PLR0915 pylint:disable=too-many-statements """Check whether the given command is permitted by any access grants, and enqueue it if permitted. For a command/subcommand to be permitted, we must have: diff --git a/nautobot_chatops/views.py b/nautobot_chatops/views.py index b629d4c9..0bd8d947 100644 --- a/nautobot_chatops/views.py +++ b/nautobot_chatops/views.py @@ -6,20 +6,19 @@ from django.contrib.auth.mixins import PermissionRequiredMixin from django.shortcuts import render - from nautobot.core.forms import restrict_form_fields from nautobot.core.utils.requests import normalize_querydict -from nautobot.core.views.generic import BulkDeleteView, ObjectDeleteView, ObjectListView, ObjectEditView, ObjectView +from nautobot.core.views.generic import BulkDeleteView, ObjectDeleteView, ObjectEditView, ObjectListView, ObjectView -from nautobot_chatops.models import CommandLog, AccessGrant, CommandToken, ChatOpsAccountLink +from nautobot_chatops import forms from nautobot_chatops.filters import ( - CommandLogFilterSet, AccessGrantFilterSet, - CommandTokenFilterSet, ChatOpsAccountLinkFilterSet, + CommandLogFilterSet, + CommandTokenFilterSet, ) -from nautobot_chatops import forms -from nautobot_chatops.tables import CommandLogTable, AccessGrantTable, CommandTokenTable, ChatOpsAccountLinkTable +from nautobot_chatops.models import AccessGrant, ChatOpsAccountLink, CommandLog, CommandToken +from nautobot_chatops.tables import AccessGrantTable, ChatOpsAccountLinkTable, CommandLogTable, CommandTokenTable class CommandLogListView(PermissionRequiredMixin, ObjectListView): diff --git a/nautobot_chatops/workers/__init__.py b/nautobot_chatops/workers/__init__.py index 19bc4322..0685d3f9 100644 --- a/nautobot_chatops/workers/__init__.py +++ b/nautobot_chatops/workers/__init__.py @@ -6,21 +6,21 @@ back to the chat using the provided ``dispatchers`` instance's generic API. """ -from datetime import datetime, timezone import inspect -from functools import wraps import logging import shlex -import pkg_resources +from datetime import datetime, timezone +from functools import wraps +import pkg_resources from django.conf import settings from django.db.models import Q from nautobot_chatops.choices import AccessGrantTypeChoices, CommandStatusChoices from nautobot_chatops.integrations.utils import ALL_INTEGRATIONS, DISABLED_INTEGRATIONS +from nautobot_chatops.metrics import command_histogram, request_command_cntr from nautobot_chatops.models import AccessGrant from nautobot_chatops.utils import create_command_log -from nautobot_chatops.metrics import request_command_cntr, command_histogram logger = logging.getLogger(__name__) @@ -57,8 +57,8 @@ def get_commands_registry(): """Populate and return the _commands_registry dictionary with all known commands, subcommands, and workers.""" - global _commands_registry # pylint: disable=global-statement,global-variable-not-assigned,invalid-name - global _registry_initialized # pylint: disable=global-statement,invalid-name + global _commands_registry # pylint: disable=global-variable-not-assigned + global _registry_initialized # pylint: disable=global-statement if _registry_initialized: # Already populated, don't regenerate it return _commands_registry diff --git a/nautobot_chatops/workers/clear.py b/nautobot_chatops/workers/clear.py index e7a34d55..70abf26e 100644 --- a/nautobot_chatops/workers/clear.py +++ b/nautobot_chatops/workers/clear.py @@ -2,7 +2,6 @@ from datetime import datetime, timezone - from nautobot_chatops.choices import CommandStatusChoices from nautobot_chatops.dispatchers.slack import SlackDispatcher from nautobot_chatops.utils import create_command_log diff --git a/nautobot_chatops/workers/helper_functions.py b/nautobot_chatops/workers/helper_functions.py index cf298103..d246af44 100644 --- a/nautobot_chatops/workers/helper_functions.py +++ b/nautobot_chatops/workers/helper_functions.py @@ -1,6 +1,5 @@ """Helper functions for nautobot.py worker.""" - NAUTOBOT_LOGO_PATH = "nautobot/NautobotLogoSquare.png" NAUTOBOT_LOGO_ALT = "Nautobot Logo" diff --git a/nautobot_chatops/workers/nautobot.py b/nautobot_chatops/workers/nautobot.py index 11fc74c1..bc5e43ae 100644 --- a/nautobot_chatops/workers/nautobot.py +++ b/nautobot_chatops/workers/nautobot.py @@ -3,28 +3,26 @@ import json import time - +from django.contrib.contenttypes.models import ContentType from django.core.exceptions import ValidationError from django.db.models import Count -from django.contrib.contenttypes.models import ContentType from django.utils.text import slugify - -from nautobot.dcim.models.device_components import Interface, FrontPort, RearPort -from nautobot.circuits.models import Circuit, CircuitType, Provider, CircuitTermination -from nautobot.dcim.models import Device, DeviceType, Location, LocationType, Manufacturer, Rack, Cable -from nautobot.ipam.models import VLAN, Prefix, VLANGroup -from nautobot.tenancy.models import Tenant +from nautobot.circuits.models import Circuit, CircuitTermination, CircuitType, Provider +from nautobot.dcim.models import Cable, Device, DeviceType, Location, LocationType, Manufacturer, Rack +from nautobot.dcim.models.device_components import FrontPort, Interface, RearPort from nautobot.extras.choices import JobResultStatusChoices -from nautobot.extras.models import Job, JobResult, Role, Status from nautobot.extras.jobs import get_job +from nautobot.extras.models import Job, JobResult, Role, Status +from nautobot.ipam.models import VLAN, Prefix, VLANGroup +from nautobot.tenancy.models import Tenant from nautobot_chatops.choices import CommandStatusChoices -from nautobot_chatops.workers import subcommand_of, handle_subcommands +from nautobot_chatops.workers import handle_subcommands, subcommand_of from nautobot_chatops.workers.helper_functions import ( add_asterisk, + menu_item_check, menu_offset_value, nautobot_logo, - menu_item_check, prompt_for_circuit_filter_type, prompt_for_device_filter_type, prompt_for_interface_filter_type, @@ -135,11 +133,7 @@ def get_filtered_connections(device, interface): status__name="Connected", termination_a_type=interface.pk, termination_b_type=interface.pk, - ).exclude( - _termination_b_device=None - ).exclude( - _termination_a_device=None - ) + ).exclude(_termination_b_device=None).exclude(_termination_a_device=None) def analyze_circuit_endpoints(endpoint): @@ -1056,6 +1050,7 @@ def filter_jobs(dispatcher, job_filters: str = ""): # We can use a Literal["ena """Get a filtered list of jobs from Nautobot that the request user have view permissions for. Args: + dispatcher (object): Chatops app dispatcher object job_filters (str): Filter job results by literals in a comma-separated string. Available filters are: enabled, installed. """ @@ -1088,6 +1083,7 @@ def get_jobs(dispatcher, kwargs: str = ""): """Get all jobs from Nautobot that the requesting user have view permissions for. Args: + dispatcher (object): Chatops app dispatcher object kwargs (str): JSON-string array of header items to be exported. (Optional, default export is: name, id, enabled) """ # Confirm kwargs is valid JSON @@ -1127,6 +1123,7 @@ def run_job(dispatcher, *args, job_name: str = "", json_string_kwargs: str = "") """Initiate a job in Nautobot by job name. Args: + dispatcher (object): Chatops app dispatcher object *args (tuple): Dispatcher form will pass job args as tuple. job_name (str): Name of Nautobot job to run. json_string_kwargs (str): JSON-string dictionary for input keyword arguments for job run. @@ -1241,6 +1238,7 @@ def run_job_form(dispatcher, job_name: str = ""): """Send job form as a multi-input dialog. On form submit it initiates the job with the form arguments. Args: + dispatcher (object): Chatops app dispatcher object job_name (str): Name of Nautobot job to run. """ # Prompt the user to pick a job if they did not specify one diff --git a/poetry.lock b/poetry.lock index 892f8b60..97d1f07c 100644 --- a/poetry.lock +++ b/poetry.lock @@ -332,29 +332,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" @@ -400,52 +377,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 = "bracex" version = "2.4" @@ -1420,22 +1351,6 @@ files = [ [package.extras] tests = ["asttokens (>=2.1.0)", "coverage", "coverage-enable-subprocess", "ipython", "littleutils", "pytest", "rich"] -[[package]] -name = "flake8" -version = "3.9.2" -description = "the modular source code checker: pep8 pyflakes and co" -optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" -files = [ - {file = "flake8-3.9.2-py2.py3-none-any.whl", hash = "sha256:bf8fd333346d844f616e8d47905ef3a3384edae6b4e9beb0c5101e25e3110907"}, - {file = "flake8-3.9.2.tar.gz", hash = "sha256:07528381786f2a6237b061f6e96610a4167b226cb926e2aa2b6b1d78057c576b"}, -] - -[package.dependencies] -mccabe = ">=0.6.0,<0.7.0" -pycodestyle = ">=2.7.0,<2.8.0" -pyflakes = ">=2.3.0,<2.4.0" - [[package]] name = "fqdn" version = "1.5.1" @@ -2239,7 +2154,7 @@ testing = ["coverage", "pyyaml"] name = "markdown-it-py" version = "3.0.0" description = "Python port of markdown-it. Markdown parsing, done right!" -optional = false +optional = true python-versions = ">=3.8" files = [ {file = "markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb"}, @@ -2259,6 +2174,20 @@ 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"] +[[package]] +name = "markdown-version-annotations" +version = "1.0.1" +description = "Markdown plugin to add custom admonitions for documenting version differences" +optional = false +python-versions = "<4.0,>=3.7" +files = [ + {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] +markdown = ">=3.3.7,<4.0.0" + [[package]] name = "markupsafe" version = "2.1.3" @@ -2357,7 +2286,7 @@ files = [ name = "mdurl" version = "0.1.2" description = "Markdown URL utilities" -optional = false +optional = true python-versions = ">=3.7" files = [ {file = "mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8"}, @@ -2487,17 +2416,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" @@ -2687,17 +2605,6 @@ files = [ {file = "multidict-6.0.4.tar.gz", hash = "sha256:3666906492efb76453c0e7b97f2cf459b0682e7402c0489a95484965dbc1da49"}, ] -[[package]] -name = "mypy-extensions" -version = "1.0.0" -description = "Type system extensions for programs checked with the mypy type checker." -optional = false -python-versions = ">=3.5" -files = [ - {file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"}, - {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"}, -] - [[package]] name = "nautobot" version = "2.0.0" @@ -2945,17 +2852,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" @@ -3345,17 +3241,6 @@ cffi = ">=1.5.0" [package.extras] idna = ["idna (>=2.1)"] -[[package]] -name = "pycodestyle" -version = "2.7.0" -description = "Python style guide checker" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -files = [ - {file = "pycodestyle-2.7.0-py2.py3-none-any.whl", hash = "sha256:514f76d918fcc0b55c6680472f0a37970994e07bbb80725808c17089be302068"}, - {file = "pycodestyle-2.7.0.tar.gz", hash = "sha256:c389c1d06bf7904078ca03399a4816f974a1d590090fecea0c63ec26ebaf1cef"}, -] - [[package]] name = "pycparser" version = "2.21" @@ -3419,17 +3304,6 @@ typing-extensions = ">=4.2.0" dotenv = ["python-dotenv (>=0.10.4)"] email = ["email-validator (>=1.0.3)"] -[[package]] -name = "pyflakes" -version = "2.3.1" -description = "passive checker of Python programs" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -files = [ - {file = "pyflakes-2.3.1-py2.py3-none-any.whl", hash = "sha256:7893783d01b8a89811dd72d7dfd4d84ff098e5eed95cfa8905b22bbffe52efc3"}, - {file = "pyflakes-2.3.1.tar.gz", hash = "sha256:f5bc8ecabc05bb9d291eb5203d6810b49040f6ff446a756326104746cc00c1db"}, -] - [[package]] name = "pygments" version = "2.16.1" @@ -4117,7 +3991,7 @@ files = [ name = "rich" version = "13.5.3" description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal" -optional = false +optional = true python-versions = ">=3.7.0" files = [ {file = "rich-13.5.3-py3-none-any.whl", hash = "sha256:9257b468badc3d347e146a4faa268ff229039d4c2d176ab0cffb4c4fbc73d5d9"}, @@ -4198,28 +4072,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]] @@ -4414,20 +4289,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 = "structlog" version = "22.3.0" @@ -4900,6 +4761,24 @@ files = [ {file = "wrapt-1.15.0.tar.gz", hash = "sha256:d06730c6aed78cee4126234cf2d071e01b44b915e725a6cb439a879ec9754a3a"}, ] +[[package]] +name = "yamllint" +version = "1.35.1" +description = "A linter for YAML files." +optional = false +python-versions = ">=3.8" +files = [ + {file = "yamllint-1.35.1-py3-none-any.whl", hash = "sha256:2e16e504bb129ff515b37823b472750b36b6de07963bd74b307341ef5ad8bdc3"}, + {file = "yamllint-1.35.1.tar.gz", hash = "sha256:7a003809f88324fd2c877734f2d575ee7881dd9043360657cc8049c809eba6cd"}, +] + +[package.dependencies] +pathspec = ">=0.5.3" +pyyaml = "*" + +[package.extras] +dev = ["doc8", "flake8", "flake8-import-order", "rstcheck[sphinx]", "sphinx"] + [[package]] name = "yarl" version = "1.9.2" @@ -5016,4 +4895,4 @@ panorama = ["defusedxml", "ipaddr", "netmiko", "netutils", "pan-os-python"] [metadata] lock-version = "2.0" python-versions = ">=3.8,<3.12" -content-hash = "4a0e18446998244cdef542b78277503106fb706c3b2b7320d94ef011aa6c22cd" +content-hash = "1dbdcfb76c1d1939ff17053c850c91e5386b606991c506de6414fcfc6a2eb459" diff --git a/pyproject.toml b/pyproject.toml index 58a6a322..b57ff6f2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -72,18 +72,19 @@ texttable = "^1.6.2" webexteamssdk = "^1.3" [tool.poetry.group.dev.dependencies] -Markdown = "*" -bandit = "*" -black = "*" -coverage = "~5.4" +coverage = "*" django-debug-toolbar = "*" -flake8 = "*" invoke = "*" ipython = "*" +Markdown = "*" +# Render custom markdown for version added/changed/remove notes +markdown-version-annotations = "1.0.1" +# Rendering docs to HTML mkdocs = "1.5.2" mkdocs-include-markdown-plugin = "6.0.3" +# Material for MkDocs theme mkdocs-material = "9.1.15" -mkdocs-version-annotations = "1.0.0" +# Automatic documentation from sources, for MkDocs mkdocstrings = "0.22.0" mkdocstrings-python = "1.5.2" prybar = "*" @@ -91,7 +92,8 @@ pylint = "*" pylint-django = "*" pylint-nautobot = "*" requests-mock = "^1.9.3" -ruff = "*" +ruff = "0.5.5" +yamllint = "*" toml = "*" towncrier = "~23.6.0" to-json-schema = "*" @@ -156,29 +158,6 @@ panorama = [ ] nautobot = ["nautobot"] -[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 load-plugins="pylint_django, pylint_nautobot" @@ -189,8 +168,6 @@ ignore=".venv" no-docstring-rgx="^(_|test_|Meta$)" [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", "nb-incorrect-base-class", @@ -223,6 +200,9 @@ target-version = "py38" [tool.ruff.lint] select = [ "D", # pydocstyle + "F", "E", "W", # flake8 + "S", # bandit + "I", # isort ] ignore = [ # warning: `one-blank-line-before-class` (D203) and `no-blank-line-before-class` (D211) are incompatible. @@ -240,20 +220,19 @@ ignore = [ "D401", # First line of docstring should be in imperative mood "D407", # Missing dashed underline after section "D416", # Section name ends in colon - - # Package specific - "D417", # Missing argument descriptions in Docstrings + "E501", # Line too long ] [tool.ruff.lint.pydocstyle] convention = "google" -[tool.ruff.per-file-ignores] +[tool.ruff.lint.per-file-ignores] "nautobot_chatops/migrations/*" = [ - "D", # pydocstyle + "D", ] "nautobot_chatops/tests/*" = [ - "D", # pydocstyle + "D", + "S" ] [build-system] diff --git a/tasks.py b/tasks.py index 62dad57a..1d34c5e2 100644 --- a/tasks.py +++ b/tasks.py @@ -161,17 +161,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) @@ -496,7 +496,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( @@ -521,9 +526,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", @@ -649,28 +656,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 @@ -690,38 +675,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 @@ -753,7 +739,7 @@ def check_migrations(context): "verbose": "Enable verbose test output.", } ) -def unittest( +def unittest( # noqa: PLR0913 context, keepdb=False, label="nautobot_chatops", @@ -801,14 +787,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...") @@ -841,14 +821,23 @@ 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"}, + ) # ------------------------------------------------------------------------------ @@ -888,13 +877,10 @@ def connect_awx_container(context, container_name="tools_awx_1"): """Connect nautobot and celery containers to awx container. Bridge network is defined in `development/ansible/docker-compose.yaml`. - To run testing awx instance, follow [instructions] (https://github.com/ansible/awx/tree/devel/tools/docker-compose#getting-started) - Before running `make docker-compose` comment out `- 8080:8080` port mapping in file `tools/docker-compose/ansible/roles/sources/templates/docker-compose.yml.j2` to avoid port conflict with nautobot. - After setting up awx, cd back to chatops repo and run `invoke connect-awx-container`. """ bridge_network = f"{context.nautobot_chatops.project_name}_awx"