From 62e52d9745c309940233c3305884d12e191df57f Mon Sep 17 00:00:00 2001 From: Adam Byczkowski Date: Thu, 5 Aug 2021 13:43:03 -0500 Subject: [PATCH 1/9] Updated pyproject --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 4482799..dcf126b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -13,7 +13,7 @@ include = [ "README.md", ] packages = [ - { include = "nautobot_plugin_chatops_cloudvision" }, + { include = "nautobot_chatops_arista_cloudvision" }, ] [tool.poetry.plugins."nautobot.workers"] From 23b418ec6ebe314f24c216985c079b013f465ade Mon Sep 17 00:00:00 2001 From: Adam Byczkowski Date: Thu, 5 Aug 2021 19:15:52 +0000 Subject: [PATCH 2/9] Removed extra cookie cutter folders and renamed package --- FAQ.md | 1 - LICENSE | 15 --------- README.md | 6 ++-- development/nautobot_config.py | 2 +- invoke.example.yml | 11 ------- mkdocs.yml | 23 ------------- .../__init__.py | 12 +++---- .../cvpgrpcutils.py | 0 .../tests/__init__.py | 1 + .../tests/test_api.py | 2 +- .../tests/test_basic.py | 2 +- .../utils.py | 2 +- .../worker.py | 4 +-- .../api/__init__.py | 1 - .../migrations/__init__.py | 0 .../tests/__init__.py | 1 - pyproject.toml | 2 +- tasks.py | 32 +++++++++---------- 18 files changed, 33 insertions(+), 84 deletions(-) delete mode 100644 FAQ.md delete mode 100644 LICENSE delete mode 100644 invoke.example.yml delete mode 100644 mkdocs.yml rename {nautobot_plugin_chatops_cloudvision => nautobot_chatops_arista_cloudvision}/__init__.py (53%) rename {nautobot_plugin_chatops_cloudvision => nautobot_chatops_arista_cloudvision}/cvpgrpcutils.py (100%) create mode 100644 nautobot_chatops_arista_cloudvision/tests/__init__.py rename {nautobot_plugin_chatops_cloudvision => nautobot_chatops_arista_cloudvision}/tests/test_api.py (94%) rename {nautobot_plugin_chatops_cloudvision => nautobot_chatops_arista_cloudvision}/tests/test_basic.py (89%) rename {nautobot_plugin_chatops_cloudvision => nautobot_chatops_arista_cloudvision}/utils.py (99%) rename {nautobot_plugin_chatops_cloudvision => nautobot_chatops_arista_cloudvision}/worker.py (99%) delete mode 100644 nautobot_plugin_chatops_cloudvision/api/__init__.py delete mode 100644 nautobot_plugin_chatops_cloudvision/migrations/__init__.py delete mode 100644 nautobot_plugin_chatops_cloudvision/tests/__init__.py diff --git a/FAQ.md b/FAQ.md deleted file mode 100644 index 318b08d..0000000 --- a/FAQ.md +++ /dev/null @@ -1 +0,0 @@ -# Frequently Asked Questions diff --git a/LICENSE b/LICENSE deleted file mode 100644 index 087f92f..0000000 --- a/LICENSE +++ /dev/null @@ -1,15 +0,0 @@ -Apache Software License 2.0 - -Copyright (c) 2021, Network to Code, LLC - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - -http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. diff --git a/README.md b/README.md index 3be2d73..46a4e1c 100644 --- a/README.md +++ b/README.md @@ -42,7 +42,7 @@ PLUGINS_CONFIG = { 'slack_api_token': os.getenv("SLACK_API_TOKEN"), 'slack_signing_secret': os.getenv("SLACK_SIGNING_SECRET") }, - 'nautobot_plugin_chatops_cloudvision' : { + 'nautobot_chatops_arista_cloudvision' : { 'cvaas_token': os.getenv("CVAAS_TOKEN"), 'cvp_username': os.getenv("CVP_USERNAME"), 'cvp_password': os.getenv("CVP_PASSWORD"), @@ -110,7 +110,7 @@ The development environment can be used in 2 ways. First, with a local poetry en The [PyInvoke](http://www.pyinvoke.org/) library is used to provide some helper commands based on the environment. There are a few configuration parameters which can be passed to PyInvoke to override the default configuration: * `nautobot_ver`: the version of Nautobot to use as a base for any built docker containers (default: 1.0.1) -* `project_name`: the default docker compose project name (default: nautobot_plugin_chatops_cloudvision) +* `project_name`: the default docker compose project name (default: nautobot_chatops_arista_cloudvision) * `python_ver`: the version of Python to use as a base for any built docker containers (default: 3.6) * `local`: a boolean flag indicating if invoke tasks should be run on the host or inside the docker containers (default: False, commands will be run in docker containers) * `compose_dir`: the full path to a directory containing the project compose files @@ -126,7 +126,7 @@ Using PyInvoke these configuration options can be overridden using [several meth ```shell --- -nautobot_plugin_chatops_cloudvision: +nautobot_chatops_arista_cloudvision: local: true compose_files: - "docker-compose.requirements.yml" diff --git a/development/nautobot_config.py b/development/nautobot_config.py index 58ee4ff..5e5bbc2 100644 --- a/development/nautobot_config.py +++ b/development/nautobot_config.py @@ -244,7 +244,7 @@ def is_truthy(arg): PAGINATE_COUNT = int(os.environ.get("PAGINATE_COUNT", 50)) # Enable installed plugins. Add the name of each plugin to the list. -PLUGINS = ["nautobot_plugin_chatops_cloudvision"] +PLUGINS = ["nautobot_chatops_arista_cloudvision"] # Plugins configuration settings. These settings are used by various plugins that the user may have installed. # Each key in the dictionary is the name of an installed plugin and its value is a dictionary of settings. diff --git a/invoke.example.yml b/invoke.example.yml deleted file mode 100644 index 4d65347..0000000 --- a/invoke.example.yml +++ /dev/null @@ -1,11 +0,0 @@ ---- -nautobot_plugin_chatops_cloudvision: - project_name: "nautobot_plugin_chatops_cloudvision" - nautobot_ver: "1.0.1" - local: false - python_ver: "3.6" - compose_dir: "development" - compose_files: - - "docker-compose.requirements.yml" - - "docker-compose.base.yml" - - "docker-compose.dev.yml" diff --git a/mkdocs.yml b/mkdocs.yml deleted file mode 100644 index c7ca29b..0000000 --- a/mkdocs.yml +++ /dev/null @@ -1,23 +0,0 @@ ---- -dev_addr: "127.0.0.1:8001" -edit_uri: "edit/main/nautobot-chatops-extension-arista/docs" -site_name: "NautobotChatopsExtensionArista Documentation" -site_url: "https://nautobot-chatops-extension-arista.readthedocs.io/" -repo_url: "https://github.com/networktocode-llc/cookiecutter-ntc/tree/main/nautobot-plugin" -python: - install: - - requirements: "docs/requirements.txt" -theme: - name: "readthedocs" - navigation_depth: 4 - hljs_languages: - - "django" - - "yaml" -extra_css: - - "extra.css" -markdown_extensions: - - "admonition" - - toc: - permalink: true -nav: - - Introduction: "index.md" diff --git a/nautobot_plugin_chatops_cloudvision/__init__.py b/nautobot_chatops_arista_cloudvision/__init__.py similarity index 53% rename from nautobot_plugin_chatops_cloudvision/__init__.py rename to nautobot_chatops_arista_cloudvision/__init__.py index 1e739bf..9735d53 100644 --- a/nautobot_plugin_chatops_cloudvision/__init__.py +++ b/nautobot_chatops_arista_cloudvision/__init__.py @@ -1,4 +1,4 @@ -"""Plugin declaration for nautobot_plugin_chatops_cloudvision.""" +"""Plugin declaration for nautobot_chatops_arista_cloudvision.""" __version__ = "0.1.0" @@ -6,14 +6,14 @@ class NautobotChatopsExtensionAristaConfig(PluginConfig): - """Plugin configuration for the nautobot_plugin_chatops_cloudvision plugin.""" + """Plugin configuration for the nautobot_chatops_arista_cloudvision plugin.""" - name = "nautobot_plugin_chatops_cloudvision" - verbose_name = "Nautobot Plugin Chatops Cloudvision" + name = "nautobot_chatops_arista_cloudvision" + verbose_name = "Nautobot Chatops Arista Cloudvision Integration" version = __version__ author = "Network to Code, LLC" - description = "Nautobot Plugin Chatops Cloudvision." - base_url = "nautobot_plugin_chatops_cloudvision" + description = "Nautobot Chatops Arista Cloudvision Integration." + base_url = "nautobot_chatops_arista_cloudvision" required_settings = [] min_version = "1.0.0" max_version = "1.9999" diff --git a/nautobot_plugin_chatops_cloudvision/cvpgrpcutils.py b/nautobot_chatops_arista_cloudvision/cvpgrpcutils.py similarity index 100% rename from nautobot_plugin_chatops_cloudvision/cvpgrpcutils.py rename to nautobot_chatops_arista_cloudvision/cvpgrpcutils.py diff --git a/nautobot_chatops_arista_cloudvision/tests/__init__.py b/nautobot_chatops_arista_cloudvision/tests/__init__.py new file mode 100644 index 0000000..cbd5b73 --- /dev/null +++ b/nautobot_chatops_arista_cloudvision/tests/__init__.py @@ -0,0 +1 @@ +"""Unit tests for nautobot_chatops_arista_cloudvision plugin.""" diff --git a/nautobot_plugin_chatops_cloudvision/tests/test_api.py b/nautobot_chatops_arista_cloudvision/tests/test_api.py similarity index 94% rename from nautobot_plugin_chatops_cloudvision/tests/test_api.py rename to nautobot_chatops_arista_cloudvision/tests/test_api.py index 2135d24..9a9a312 100644 --- a/nautobot_plugin_chatops_cloudvision/tests/test_api.py +++ b/nautobot_chatops_arista_cloudvision/tests/test_api.py @@ -1,4 +1,4 @@ -"""Unit tests for nautobot_plugin_chatops_cloudvision.""" +"""Unit tests for nautobot_chatops_arista_cloudvision.""" from django.contrib.auth import get_user_model from django.test import TestCase from django.urls import reverse diff --git a/nautobot_plugin_chatops_cloudvision/tests/test_basic.py b/nautobot_chatops_arista_cloudvision/tests/test_basic.py similarity index 89% rename from nautobot_plugin_chatops_cloudvision/tests/test_basic.py rename to nautobot_chatops_arista_cloudvision/tests/test_basic.py index 883665d..5795f91 100644 --- a/nautobot_plugin_chatops_cloudvision/tests/test_basic.py +++ b/nautobot_chatops_arista_cloudvision/tests/test_basic.py @@ -3,7 +3,7 @@ import os import toml -from nautobot_plugin_chatops_cloudvision import __version__ as project_version +from nautobot_chatops_arista_cloudvision import __version__ as project_version class TestVersion(unittest.TestCase): diff --git a/nautobot_plugin_chatops_cloudvision/utils.py b/nautobot_chatops_arista_cloudvision/utils.py similarity index 99% rename from nautobot_plugin_chatops_cloudvision/utils.py rename to nautobot_chatops_arista_cloudvision/utils.py index 8081f02..9dd9bb7 100644 --- a/nautobot_plugin_chatops_cloudvision/utils.py +++ b/nautobot_chatops_arista_cloudvision/utils.py @@ -11,7 +11,7 @@ fullpath = os.path.abspath(__file__) directory = os.path.dirname(fullpath) -PLUGIN_SETTINGS = settings.PLUGINS_CONFIG["nautobot_plugin_chatops_cloudvision"] +PLUGIN_SETTINGS = settings.PLUGINS_CONFIG["nautobot_chatops_arista_cloudvision"] CVAAS_TOKEN = PLUGIN_SETTINGS.get("CVAAS_TOKEN") CVAAS_ADDR = "apiserver.arista.io:443" diff --git a/nautobot_plugin_chatops_cloudvision/worker.py b/nautobot_chatops_arista_cloudvision/worker.py similarity index 99% rename from nautobot_plugin_chatops_cloudvision/worker.py rename to nautobot_chatops_arista_cloudvision/worker.py index 9fdd752..4ba107c 100644 --- a/nautobot_plugin_chatops_cloudvision/worker.py +++ b/nautobot_chatops_arista_cloudvision/worker.py @@ -6,7 +6,7 @@ from django.conf import settings from nautobot_chatops.workers import subcommand_of, handle_subcommands # pylint: disable=import-error from nautobot_chatops.choices import CommandStatusChoices # pylint: disable=import-error -import nautobot_plugin_chatops_cloudvision.cvpgrpcutils as grpcutils +import nautobot_chatops_arista_cloudvision.cvpgrpcutils as grpcutils from .utils import ( prompt_for_events_filter, prompt_for_device_or_container, @@ -36,7 +36,7 @@ logger = logging.getLogger("rq.worker") dir_path = os.path.dirname(os.path.realpath(__file__)) -PLUGIN_SETTINGS = settings.PLUGINS_CONFIG["nautobot_plugin_chatops_cloudvision"] +PLUGIN_SETTINGS = settings.PLUGINS_CONFIG["nautobot_chatops_arista_cloudvision"] def cloudvision_logo(dispatcher): diff --git a/nautobot_plugin_chatops_cloudvision/api/__init__.py b/nautobot_plugin_chatops_cloudvision/api/__init__.py deleted file mode 100644 index cbaaf61..0000000 --- a/nautobot_plugin_chatops_cloudvision/api/__init__.py +++ /dev/null @@ -1 +0,0 @@ -"""REST API module for nautobot_plugin_chatops_cloudvision plugin.""" diff --git a/nautobot_plugin_chatops_cloudvision/migrations/__init__.py b/nautobot_plugin_chatops_cloudvision/migrations/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/nautobot_plugin_chatops_cloudvision/tests/__init__.py b/nautobot_plugin_chatops_cloudvision/tests/__init__.py deleted file mode 100644 index 0e5dc55..0000000 --- a/nautobot_plugin_chatops_cloudvision/tests/__init__.py +++ /dev/null @@ -1 +0,0 @@ -"""Unit tests for nautobot_plugin_chatops_cloudvision plugin.""" diff --git a/pyproject.toml b/pyproject.toml index dcf126b..2f0e363 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -17,7 +17,7 @@ packages = [ ] [tool.poetry.plugins."nautobot.workers"] -"cloudvision" = "nautobot_plugin_chatops_cloudvision.worker:cloudvision_chatbot" +"cloudvision" = "nautobot_chatops_arista_cloudvision.worker:cloudvision_chatbot" [tool.poetry.dependencies] # Used for local development diff --git a/tasks.py b/tasks.py index a28fd1e..7bb8683 100644 --- a/tasks.py +++ b/tasks.py @@ -33,13 +33,13 @@ def is_truthy(arg): # Use pyinvoke configuration for default values, see http://docs.pyinvoke.org/en/stable/concepts/configuration.html -# Variables may be overwritten in invoke.yml or by the environment variables INVOKE_NAUTOBOT_PLUGIN_CHATOPS_CLOUDVISION_xxx -namespace = Collection("nautobot_plugin_chatops_cloudvision") +# Variables may be overwritten in invoke.yml or by the environment variables INVOKE_NAUTOBOT_CHATOPS_ARISTA_CLOUDVISION_xxx +namespace = Collection("nautobot_chatops_arista_cloudvision") namespace.configure( { - "nautobot_plugin_chatops_cloudvision": { + "nautobot_chatops_arista_cloudvision": { "nautobot_ver": "1.0.1", - "project_name": "nautobot_plugin_chatops_cloudvision", + "project_name": "nautobot_chatops_arista_cloudvision", "python_ver": "3.6", "local": False, "compose_dir": os.path.join(os.path.dirname(__file__), "development"), @@ -82,12 +82,12 @@ def docker_compose(context, command, **kwargs): **kwargs: Passed through to the context.run() call. """ build_env = { - "NAUTOBOT_VER": context.nautobot_plugin_chatops_cloudvision.nautobot_ver, - "PYTHON_VER": context.nautobot_plugin_chatops_cloudvision.python_ver, + "NAUTOBOT_VER": context.nautobot_chatops_arista_cloudvision.nautobot_ver, + "PYTHON_VER": context.nautobot_chatops_arista_cloudvision.python_ver, } - compose_command = f'docker-compose --project-name {context.nautobot_plugin_chatops_cloudvision.project_name} --project-directory "{context.nautobot_plugin_chatops_cloudvision.compose_dir}"' - for compose_file in context.nautobot_plugin_chatops_cloudvision.compose_files: - compose_file_path = os.path.join(context.nautobot_plugin_chatops_cloudvision.compose_dir, compose_file) + compose_command = f'docker-compose --project-name {context.nautobot_chatops_arista_cloudvision.project_name} --project-directory "{context.nautobot_chatops_arista_cloudvision.compose_dir}"' + for compose_file in context.nautobot_chatops_arista_cloudvision.compose_files: + compose_file_path = os.path.join(context.nautobot_chatops_arista_cloudvision.compose_dir, compose_file) compose_command += f' -f "{compose_file_path}"' compose_command += f" {command}" print(f'Running docker-compose command "{command}"') @@ -96,7 +96,7 @@ def docker_compose(context, command, **kwargs): def run_command(context, command, **kwargs): """Wrapper to run a command locally or inside the nautobot container.""" - if is_truthy(context.nautobot_plugin_chatops_cloudvision.local): + if is_truthy(context.nautobot_chatops_arista_cloudvision.local): context.run(command, **kwargs) else: # Check if netbox is running, no need to start another netbox container to run a command @@ -128,7 +128,7 @@ def build(context, force_rm=False, cache=True): if force_rm: command += " --force-rm" - print(f"Building Nautobot with Python {context.nautobot_plugin_chatops_cloudvision.python_ver}...") + print(f"Building Nautobot with Python {context.nautobot_chatops_arista_cloudvision.python_ver}...") docker_compose(context, command) @@ -220,7 +220,7 @@ def createsuperuser(context, user="admin"): ) def makemigrations(context, name=""): """Perform makemigrations operation in Django.""" - command = "nautobot-server makemigrations nautobot_plugin_chatops_cloudvision" + command = "nautobot-server makemigrations nautobot_chatops_arista_cloudvision" if name: command += f" --name {name}" @@ -292,7 +292,7 @@ def hadolint(context): @task def pylint(context): """Run pylint code analysis.""" - command = 'pylint --init-hook "import nautobot; nautobot.setup()" --rcfile pyproject.toml nautobot_plugin_chatops_cloudvision' + command = 'pylint --init-hook "import nautobot; nautobot.setup()" --rcfile pyproject.toml nautobot_chatops_arista_cloudvision' run_command(context, command) @@ -327,7 +327,7 @@ def check_migrations(context): "buffer": "Discard output from passing tests", } ) -def unittest(context, keepdb=False, label="nautobot_plugin_chatops_cloudvision", failfast=False, buffer=True): +def unittest(context, keepdb=False, label="nautobot_chatops_arista_cloudvision", failfast=False, buffer=True): """Run Nautobot unit tests.""" command = f"coverage run --module nautobot.core.cli test {label}" @@ -343,7 +343,7 @@ def unittest(context, keepdb=False, label="nautobot_plugin_chatops_cloudvision", @task def unittest_coverage(context): """Report on code test coverage as measured by 'invoke unittest'.""" - command = "coverage report --skip-covered --include 'nautobot_plugin_chatops_cloudvision/*' --omit *migrations*" + command = "coverage report --skip-covered --include 'nautobot_chatops_arista_cloudvision/*' --omit *migrations*" run_command(context, command) @@ -356,7 +356,7 @@ def unittest_coverage(context): def tests(context, failfast=False): """Run all tests for this plugin.""" # If we are not running locally, start the docker containers so we don't have to for each test - if not is_truthy(context.nautobot_plugin_chatops_cloudvision.local): + if not is_truthy(context.nautobot_chatops_arista_cloudvision.local): print("Starting Docker Containers...") start(context) # Sorted loosely from fastest to slowest From ca83974dbf6db29257d03eebb3cf5f9441c9bef7 Mon Sep 17 00:00:00 2001 From: Adam Byczkowski Date: Thu, 5 Aug 2021 20:54:25 +0000 Subject: [PATCH 3/9] Cleaned up cvaas login --- nautobot_chatops_arista_cloudvision/utils.py | 18 ++++++++++-------- nautobot_chatops_arista_cloudvision/worker.py | 3 +-- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/nautobot_chatops_arista_cloudvision/utils.py b/nautobot_chatops_arista_cloudvision/utils.py index 9dd9bb7..825350b 100644 --- a/nautobot_chatops_arista_cloudvision/utils.py +++ b/nautobot_chatops_arista_cloudvision/utils.py @@ -13,15 +13,17 @@ PLUGIN_SETTINGS = settings.PLUGINS_CONFIG["nautobot_chatops_arista_cloudvision"] -CVAAS_TOKEN = PLUGIN_SETTINGS.get("CVAAS_TOKEN") +CVAAS_TOKEN = PLUGIN_SETTINGS.get("cvaas_token") +if CVAAS_TOKEN: + with open("cvaas_token.txt", "w") as tf: + tf.write(PLUGIN_SETTINGS.get("cvaas_token")) CVAAS_ADDR = "apiserver.arista.io:443" -CVAAS_TOKEN_PATH = f"{directory}/cvaas_token.txt" -CVP_USERNAME = PLUGIN_SETTINGS.get("CVP_USERNAME") -CVP_PASSWORD = PLUGIN_SETTINGS.get("CVP_PASSWORD") -CVP_HOST = PLUGIN_SETTINGS.get("CVP_HOST") -CVP_INSECURE = PLUGIN_SETTINGS.get("CVP_INSECURE") -ON_PREM = PLUGIN_SETTINGS.get("ON_PREM") +CVP_USERNAME = PLUGIN_SETTINGS.get("cvp_username") +CVP_PASSWORD = PLUGIN_SETTINGS.get("cvp_password") +CVP_HOST = PLUGIN_SETTINGS.get("cvp_host") +CVP_INSECURE = PLUGIN_SETTINGS.get("cvp_insecure") +ON_PREM = PLUGIN_SETTINGS.get("on_prem") CVP_TOKEN_PATH = f"{directory}/token.txt" CRT_FILE_PATH = f"{directory}/cvp.crt" @@ -60,7 +62,7 @@ def prompt_for_image_bundle_name_or_all(action_id, help_text, dispatcher): def connect_cvp(): """Connect to an instance of Cloudvision.""" - if ON_PREM: + if ON_PREM.lower() == "true": clnt = CvpClient() clnt.connect([CVP_HOST], CVP_USERNAME, CVP_PASSWORD) token_request = requests.post( diff --git a/nautobot_chatops_arista_cloudvision/worker.py b/nautobot_chatops_arista_cloudvision/worker.py index 4ba107c..6d5205f 100644 --- a/nautobot_chatops_arista_cloudvision/worker.py +++ b/nautobot_chatops_arista_cloudvision/worker.py @@ -46,7 +46,7 @@ def cloudvision_logo(dispatcher): def check_credentials(dispatcher): """Check whether to use on prem or cloud instance of Cloudvision.""" - if PLUGIN_SETTINGS.get("on_prem"): + if PLUGIN_SETTINGS.get("on_prem").lower() == "true": if ( not PLUGIN_SETTINGS.get("cvp_username") and not PLUGIN_SETTINGS.get("cvp_password") @@ -68,7 +68,6 @@ def cloudvision_chatbot(subcommand, **kwargs): """Interact with cloudvision.""" return handle_subcommands("cloudvision", subcommand, **kwargs) - @subcommand_of("cloudvision") def get_devices_in_container(dispatcher, container_name=None): """Get a list of devices in a Cloudvision container.""" From 22faca693f34e0317318f5ba811ef8992f55a68d Mon Sep 17 00:00:00 2001 From: Adam Byczkowski Date: Thu, 5 Aug 2021 16:26:10 -0500 Subject: [PATCH 4/9] Readme update --- README.md | 57 +++++++++++++++++++------------------------------------ 1 file changed, 19 insertions(+), 38 deletions(-) diff --git a/README.md b/README.md index 46a4e1c..200707e 100644 --- a/README.md +++ b/README.md @@ -1,32 +1,13 @@ -# Nautobot Chatops Extension Arista +# Nautobot ChatOps Extension Arista -An extension for [Nautobot](https://github.com/nautobot/nautobot) [Chatops Plugin](https://github.com/nautobot/nautobot-plugin-chatops/) +An extension for [Nautobot](https://github.com/nautobot/nautobot) [ChatOps Plugin](https://github.com/nautobot/nautobot-plugin-chatops/) -The extension is available as a Python package in PyPI and can be installed with pip - -```shell -pip install git+https://github.com/nautobot/nautobot-plugin-chatops-arista-cloudvision.git -``` - -This ChatOps Extension to Nautobot ChatOps Plugin requires environment variables to be set up depending on if you are using a CVAAS (Cloudvision as a Service) or Cloudvision on-premise. - -For CVAAS the following environment variables must be set. - -- `CVAAS_TOKEN`: Token generated from CVAAS service account. Documentation for that process can be found [here](https://www.arista.com/assets/data/pdf/qsg/qsg-books/QS_CloudVision_as_a_Service.pdf) in section 1.7 -For on premise instance of Cloudvision, these environment variables must be set. -- `CVP_USERNAME`: The username that will be used to authenticate to Cloudvision. -- `CVP_PASSWORD`: The password for the configured username. -- `CVP_HOST`: The IP or hostname of the on premise Cloudvision appliance. -- `CVP_INSECURE`: If this is set to `True`, the appliance cert will be downloaded and automatically trusted. Otherwise, the appliance is expected to have a valid certificate. -- `ON_PREM`: By default this is set to False, this must be changed to `True` if using an on-prem instance of Cloudvision. - -Once you have updated your environment file, restart both nautobot and nautobot-worker +The extension is available as a Python package in PyPI and can be installed with pip -``` -$ sudo systemctl daemon-reload -$ sudo systemctl restart nautobot nautobot-worker +```shell +pip install nautobot-chatops-arista-cloudvision ``` ## Usage @@ -53,19 +34,19 @@ PLUGINS_CONFIG = { } ``` -After that, you must update environment variables depending on if you are using a CVAAS (Cloudvision as a Service) or Cloudvision on-premise. To update environment variables in Nautobot check out our blog post [here](http://blog.networktocode.com/post/creating-custom-chat-commands-using-nautobot-chatops/) +After that, you must update environment variables depending on if you are using a CVaaS (CloudVision as a Service) or CloudVision on-premise. To update environment variables in Nautobot check out our blog post [here](http://blog.networktocode.com/post/creating-custom-chat-commands-using-nautobot-chatops/) For CVAAS the following environment variables must be set. - `CVAAS_TOKEN`: Token generated from CVAAS service account. Documentation for that process can be found [here](https://www.arista.com/assets/data/pdf/qsg/qsg-books/QS_CloudVision_as_a_Service.pdf) in section 1.7 -For on premise instance of Cloudvision, these environment variables must be set. +For on premise instance of CloudVision, these environment variables must be set. -- `CVP_USERNAME`: The username that will be used to authenticate to Cloudvision. +- `CVP_USERNAME`: The username that will be used to authenticate to CloudVision. - `CVP_PASSWORD`: The password for the configured username. -- `CVP_HOST`: The IP or hostname of the on premise Cloudvision appliance. +- `CVP_HOST`: The IP or hostname of the on premise CloudVision appliance. - `CVP_INSECURE`: If this is set to `True`, the appliance cert will be downloaded and automatically trusted. Otherwise, the appliance is expected to have a valid certificate. -- `ON_PREM`: By default this is set to False, this must be changed to `True` if using an on-prem instance of Cloudvision. +- `ON_PREM`: By default this is set to False, this must be changed to `True` if using an on-prem instance of CloudVision. Once you have updated your environment file, restart both nautobot and nautobot-worker @@ -87,9 +68,15 @@ The following commands are available: - `get-task-logs [task-id]`: Get the logs of the specified task. - `get-applied-configlets [filter-type] [filter-value]`: Get applied configlets to either a specified container or device. - `get-active-events [filter-type] [filter-value] [start-time] [end-time]`: Get active events in a given time frame. Filter-type can be filtered by device, type or severity. Filter-value is dynamically created based on the filter-type. Start-time accepts ISO time format as well as relative time inputs. Examples of that are `-2w`, `-2d`, `-2h` which will go back two weeks, two days and two hours, respectively. -- `get-applied-image-bundles [filter-type] [image-bundle-name]`: Gets the devices and containers an image bundle is applied to. Can also specify the `all` parameter to get a list of all the image bundles on Cloudvision. +- `get-tags [device-name]`: Get system or user tags assigned to a device. - `get-device-cve [device-name]`: Gets all the CVEs of the specified device. Can also specifiy the `all` parameter to get a count of CVE account for each device. +## Screenshots + +![cloudvision_get_active_events](https://user-images.githubusercontent.com/38091261/128059429-4e4dc269-2113-411b-9721-9ef281a361c5.PNG) +![cloudvision_get_configlet](https://user-images.githubusercontent.com/38091261/128059458-d6395d63-6909-4219-9dcb-dff1801cbda2.PNG) +![cloudvision_get_device_cve](https://user-images.githubusercontent.com/38091261/128059481-2ff60896-81e4-46ae-992b-7d179403fe8f.PNG) + ## Contributing Pull requests are welcomed and automatically built and tested against multiple version of Python and multiple version of Nautobot through TravisCI. @@ -103,6 +90,8 @@ The project is following Network to Code software development guideline and is l ### Development Environment +> A slack workspace is needed to test in a development environment. + The development environment can be used in 2 ways. First, with a local poetry environment if you wish to develop outside of Docker. Second, inside of a docker container. #### Invoke tasks @@ -208,14 +197,6 @@ Each command can be executed with `invoke `. Environment variables `INV unittest Run Django unit tests for the plugin. ``` -## Screenshots - -![cloudvision_get_active_events](https://user-images.githubusercontent.com/38091261/128059429-4e4dc269-2113-411b-9721-9ef281a361c5.PNG) -![cloudvision_get_configlet](https://user-images.githubusercontent.com/38091261/128059458-d6395d63-6909-4219-9dcb-dff1801cbda2.PNG) -![cloudvision_get_device_cve](https://user-images.githubusercontent.com/38091261/128059481-2ff60896-81e4-46ae-992b-7d179403fe8f.PNG) - - - ## Questions For any questions or comments, please check the [FAQ](FAQ.md) first and feel free to swing by the [Network to Code slack channel](https://networktocode.slack.com/) (channel #networktocode). From cedb0e3cc13b9a99c9830d8076190c9c2495a4a4 Mon Sep 17 00:00:00 2001 From: Adam Byczkowski <38091261+qduk@users.noreply.github.com> Date: Thu, 5 Aug 2021 16:33:02 -0500 Subject: [PATCH 5/9] Update README.md --- README.md | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 200707e..b21fe49 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,9 @@ # Nautobot ChatOps Extension Arista -An extension for [Nautobot](https://github.com/nautobot/nautobot) [ChatOps Plugin](https://github.com/nautobot/nautobot-plugin-chatops/) +An plugin for [Nautobot](https://github.com/nautobot/nautobot) [ChatOps Plugin](https://github.com/nautobot/nautobot-plugin-chatops/) +## Instalation The extension is available as a Python package in PyPI and can be installed with pip @@ -10,10 +11,6 @@ The extension is available as a Python package in PyPI and can be installed with pip install nautobot-chatops-arista-cloudvision ``` -## Usage - -### Nautobot Config - You must first update the Nautobot configuration file with a new entry in the `PLUGINS_CONFIG` dictionary. ```python @@ -54,6 +51,7 @@ Once you have updated your environment file, restart both nautobot and nautobot- $ sudo systemctl daemon-reload $ sudo systemctl restart nautobot nautobot-worker ``` +## Usage ### Command setup From c40162366c7cf846e687f9dee392b3b5ecdd61d4 Mon Sep 17 00:00:00 2001 From: Adam Byczkowski Date: Fri, 6 Aug 2021 04:10:53 +0000 Subject: [PATCH 6/9] Added ephermeral to standby output --- nautobot_chatops_arista_cloudvision/utils.py | 69 +++++++------------ nautobot_chatops_arista_cloudvision/worker.py | 39 ++++++----- 2 files changed, 46 insertions(+), 62 deletions(-) diff --git a/nautobot_chatops_arista_cloudvision/utils.py b/nautobot_chatops_arista_cloudvision/utils.py index 825350b..83db016 100644 --- a/nautobot_chatops_arista_cloudvision/utils.py +++ b/nautobot_chatops_arista_cloudvision/utils.py @@ -14,9 +14,6 @@ PLUGIN_SETTINGS = settings.PLUGINS_CONFIG["nautobot_chatops_arista_cloudvision"] CVAAS_TOKEN = PLUGIN_SETTINGS.get("cvaas_token") -if CVAAS_TOKEN: - with open("cvaas_token.txt", "w") as tf: - tf.write(PLUGIN_SETTINGS.get("cvaas_token")) CVAAS_ADDR = "apiserver.arista.io:443" CVP_USERNAME = PLUGIN_SETTINGS.get("cvp_username") @@ -233,11 +230,11 @@ def get_severity_events(filter_value): def get_active_events_data(apiserverAddr=None, token=None, certs=None, key=None, ca=None): # pylint: disable=invalid-name """Gets a list of active event types from CVP.""" - if ON_PREM: + if check_on_prem(): apiserverAddr = CVP_HOST else: apiserverAddr = CVAAS_ADDR - token = CVAAS_TOKEN_PATH + token = CVAAS_TOKEN pathElts = [ "events", @@ -245,7 +242,7 @@ def get_active_events_data(apiserverAddr=None, token=None, certs=None, key=None, ] query = [create_query([(pathElts, [])], "analytics")] events = [] - with GRPCClient(apiserverAddr, token=token, key=key, ca=ca, certs=certs) as client: + with GRPCClient(apiserverAddr, tokenValue=token, key=key, ca=ca, certs=certs) as client: for batch in client.get(query): for notif in batch["notifications"]: for info in notif["updates"].values(): @@ -263,11 +260,11 @@ def get_active_events_data_filter( ): # pylint: disable=invalid-name,too-many-arguments,too-many-locals,too-many-branches,no-member """Gets a list of active event types from CVP in a specific time range.""" - if ON_PREM: + if check_on_prem(): apiserverAddr = CVP_HOST else: apiserverAddr = CVAAS_ADDR - token = CVAAS_TOKEN_PATH + token = CVAAS_TOKEN start = Timestamp() if isinstance(start_time, str): @@ -286,7 +283,7 @@ def get_active_events_data_filter( ] query = [create_query([(pathElts, [])], "analytics")] events = [] - with GRPCClient(apiserverAddr, token=token, key=key, ca=ca, certs=certs) as client: + with GRPCClient(apiserverAddr, tokenValue=token, key=key, ca=ca, certs=certs) as client: for batch in client.get(query, start=start, end=end): for notif in batch["notifications"]: for info in notif["updates"].values(): @@ -311,11 +308,11 @@ def get_active_events_data_filter( def get_active_severity_types(apiserverAddr=None, token=None, certs=None, key=None, ca=None): """Gets a list of active event types from CVP.""" # pylint: disable=invalid-name - if ON_PREM: + if check_on_prem(): apiserverAddr = CVP_HOST else: apiserverAddr = CVAAS_ADDR - token = CVAAS_TOKEN_PATH + token = CVAAS_TOKEN pathElts = [ "events", @@ -323,7 +320,7 @@ def get_active_severity_types(apiserverAddr=None, token=None, certs=None, key=No ] query = [create_query([(pathElts, [])], "analytics")] event_types = [] - with GRPCClient(apiserverAddr, token=token, key=key, ca=ca, certs=certs) as client: + with GRPCClient(apiserverAddr, tokenValue=token, key=key, ca=ca, certs=certs) as client: for batch in client.get(query): for notif in batch["notifications"]: for info in notif["updates"]: @@ -339,43 +336,19 @@ def get_applied_tags(device_id): result = clnt.post(tag_url, data=payload) return result - -def get_image_bundles(): - """Get image bundle from cloudvision.""" - clnt = connect_cvp() - result = clnt.api.get_image_bundles() - return result["data"] - - -def get_images(image_bundle_name=None): - """Get images that are on Cloudvision.""" - clnt = connect_cvp() - if not image_bundle_name: - result = clnt.api.get_images() - return result["data"] - combined_applied = {} - url_containers = f"/image/getImageBundleAppliedContainers.do?imageName={image_bundle_name}&startIndex=0&endIndex=0" - url_devices = f"/image/getImageBundleAppliedDevices.do?imageName={image_bundle_name}&startIndex=0&endIndex=0" - result_containers = clnt.get(url_containers) - result_devices = clnt.get(url_devices) - combined_applied["containers"] = result_containers["data"] - combined_applied["devices"] = result_devices["data"] - return combined_applied - - def get_device_bugs_data(device_id, apiserverAddr=None, token=None, certs=None, key=None, ca=None): """Get bugs associated with a device.""" # pylint: disable=invalid-name,too-many-arguments - if ON_PREM: + if check_on_prem(): apiserverAddr = CVP_HOST else: apiserverAddr = CVAAS_ADDR - token = CVAAS_TOKEN_PATH + token = CVAAS_TOKEN pathElts = ["tags", "BugAlerts", "devices"] query = [create_query([(pathElts, [])], "analytics")] bugs = [] - with GRPCClient(apiserverAddr, token=token, key=key, ca=ca, certs=certs) as client: + with GRPCClient(apiserverAddr, tokenValue=token, key=key, ca=ca, certs=certs) as client: for batch in client.get(query): for notif in batch["notifications"]: if notif["updates"].get(device_id): @@ -386,11 +359,12 @@ def get_device_bugs_data(device_id, apiserverAddr=None, token=None, certs=None, def get_bug_info(bug_id, apiserverAddr=None, token=None): """Get detailed information about a bug given its identifier.""" # pylint: disable=invalid-name - if ON_PREM: + if check_on_prem(): apiserverAddr = CVP_HOST else: apiserverAddr = CVAAS_ADDR - token = CVAAS_TOKEN_PATH + token = CVAAS_TOKEN + pathElts = [ "BugAlerts", "bugs", @@ -398,7 +372,7 @@ def get_bug_info(bug_id, apiserverAddr=None, token=None): ] query = [create_query([(pathElts, [])], "analytics")] bug_info = {} - with GRPCClient(apiserverAddr, token=token) as client: + with GRPCClient(apiserverAddr, tokenValue=token) as client: for batch in client.get(query): for notif in batch["notifications"]: bug_info["identifier"] = bug_id @@ -411,17 +385,22 @@ def get_bug_info(bug_id, apiserverAddr=None, token=None): def get_bug_device_report(apiserverAddr=None, token=None): """Get how many bugs each device has.""" # pylint: disable=invalid-name - if ON_PREM: + if check_on_prem(): apiserverAddr = CVP_HOST else: apiserverAddr = CVAAS_ADDR - token = CVAAS_TOKEN_PATH + token = CVAAS_TOKEN pathElts = ["BugAlerts", "DevicesBugsCount"] query = [create_query([(pathElts, [])], "analytics")] bug_count = {} - with GRPCClient(apiserverAddr, token=token) as client: + with GRPCClient(apiserverAddr, tokenValue=token) as client: for batch in client.get(query): for notif in batch["notifications"]: bug_count = notif["updates"] return bug_count + +def check_on_prem(): + if ON_PREM.lower() == "false": + return False + return True diff --git a/nautobot_chatops_arista_cloudvision/worker.py b/nautobot_chatops_arista_cloudvision/worker.py index 6d5205f..a88204f 100644 --- a/nautobot_chatops_arista_cloudvision/worker.py +++ b/nautobot_chatops_arista_cloudvision/worker.py @@ -50,15 +50,15 @@ def check_credentials(dispatcher): if ( not PLUGIN_SETTINGS.get("cvp_username") and not PLUGIN_SETTINGS.get("cvp_password") - and not PLUGIN_SETTINGS.get("cvp_url") + and not PLUGIN_SETTINGS.get("cvp_host") ): dispatcher.send_warning( - "Please ensure environment variables CVP_USERNAME, CVP_PASSWORD and CVP_URL are set." + "Please ensure environment variables CVP_USERNAME, CVP_PASSWORD and CVP_URL are set and your nautobot config file is updated." ) return False else: if not PLUGIN_SETTINGS.get("cvaas_token"): - dispatcher.send_warning("Please ensure environment variable CVAAS_TOKEN is set.") + dispatcher.send_warning("Please ensure environment variable CVAAS_TOKEN is set and your nautobot config file is updated.") return False return True @@ -68,6 +68,11 @@ def cloudvision_chatbot(subcommand, **kwargs): """Interact with cloudvision.""" return handle_subcommands("cloudvision", subcommand, **kwargs) +@subcommand_of("cloudvision") +def test(dispatcher): + """Test print""" + dispatcher.send_markdown(PLUGIN_SETTINGS.get("cvp_host")) + @subcommand_of("cloudvision") def get_devices_in_container(dispatcher, container_name=None): """Get a list of devices in a Cloudvision container.""" @@ -82,7 +87,7 @@ def get_devices_in_container(dispatcher, container_name=None): return False dispatcher.send_markdown( - f"Standby {dispatcher.user_mention()}, I'm getting the devices from the container {container_name}." + f"Standby {dispatcher.user_mention()}, I'm getting the devices from the container {container_name}.", ephemeral=True ) devices = get_cloudvision_container_devices(container_name) @@ -120,7 +125,7 @@ def get_configlet(dispatcher, configlet_name=None): return False dispatcher.send_markdown( - f"Standby {dispatcher.user_mention()}, I'm getting the configuration of the {configlet_name} configlet." + f"Standby {dispatcher.user_mention()}, I'm getting the configuration of the {configlet_name} configlet.", ephemeral=True ) config = get_configlet_config(configlet_name) @@ -147,7 +152,7 @@ def get_device_configuration(dispatcher, device_name=None): return False dispatcher.send_markdown( - f"Stand by {dispatcher.user_mention()}, I'm getting the running configuration for {device_name}." + f"Stand by {dispatcher.user_mention()}, I'm getting the running configuration for {device_name}.", ephemeral=True ) device = next(device for device in device_list if device["hostname"] == device_name) @@ -176,7 +181,7 @@ def get_task_logs(dispatcher, task_id=None): return False - dispatcher.send_markdown(f"Stand by {dispatcher.user_mention()}, I'm getting the logs of task {task_id}.") + dispatcher.send_markdown(f"Stand by {dispatcher.user_mention()}, I'm getting the logs of task {task_id}.", ephemeral=True) single_task = next(task for task in task_list if task["workOrderId"] == task_id) single_task_cc_id = single_task.get("ccIdV2") @@ -244,7 +249,7 @@ def get_applied_configlets(dispatcher, filter_type=None, filter_value=None): applied_configlets = get_applied_configlets_device_id(filter_value, device_list) dispatcher.send_markdown( - f"Stand by {dispatcher.user_mention()}, I'm getting the configs applied to the {filter_type} {filter_value}." + f"Stand by {dispatcher.user_mention()}, I'm getting the configs applied to the {filter_type} {filter_value}.", ephemeral=True ) dispatcher.send_blocks( dispatcher.command_response_header( @@ -321,7 +326,7 @@ def get_active_events(dispatcher, filter_type=None, filter_value=None, start_tim if not start_time: dispatcher.prompt_for_text( f"cloudvision get-active-events {filter_type} {filter_value}", - "Enter start time in ISO format.", + "Enter start time in ISO format or enter a relative time using 'h' for hours, 'd' for days, and 'w' for weeks. Ex: '-2d'", "Start Time", ) return False @@ -329,7 +334,7 @@ def get_active_events(dispatcher, filter_type=None, filter_value=None, start_tim if not end_time: dispatcher.prompt_for_text( f"cloudvision get-active-events {filter_type} {filter_value} {start_time}", - "Enter end time in ISO format.", + "Enter start time in ISO format or enter a relative time using 'h' for hours, 'd' for days, and 'w' for weeks. Ex: '-2d'. You may also type 'now' to use the current time.", "End Time", ) return False @@ -353,24 +358,24 @@ def get_active_events(dispatcher, filter_type=None, filter_value=None, start_tim filter_type=filter_type, filter_value=filter_value, start_time=start_time, end_time=end_time ) dispatcher.send_markdown( - f"Stand by {dispatcher.user_mention()}, I'm getting the desired events with severity level {filter_value}." + f"Stand by {dispatcher.user_mention()}, I'm getting the desired events with severity level {filter_value}.", ephemeral=True ) elif filter_type == "device": active_events = get_active_events_data_filter( filter_type=filter_type, filter_value=filter_value, start_time=start_time, end_time=end_time ) dispatcher.send_markdown( - f"Stand by {dispatcher.user_mention()}, I'm getting the desired events with for device {filter_value}." + f"Stand by {dispatcher.user_mention()}, I'm getting the desired events with for device {filter_value}.", ephemeral=True ) elif filter_type == "type": active_events = get_active_events_data_filter( filter_type=filter_type, filter_value=filter_value, start_time=start_time, end_time=end_time ) dispatcher.send_markdown( - f"Stand by {dispatcher.user_mention()}, I'm getting the desired events with for event type {filter_value}." + f"Stand by {dispatcher.user_mention()}, I'm getting the desired events with for event type {filter_value}.", ephemeral=True ) - dispatcher.send_markdown(f"Stand by {dispatcher.user_mention()}, I'm getting those events.") + dispatcher.send_markdown(f"Stand by {dispatcher.user_mention()}, I'm getting those events.", ephemeral=True) header = ["Title", "Severity", "Description", "Device"] rows = [(event["title"], event["severity"], event["description"], event["deviceId"]) for event in active_events] @@ -408,14 +413,14 @@ def get_tags(dispatcher, device_name=None): dispatcher.prompt_from_menu("cloudvision get-tags", "Select a device.", choices) return False - dispatcher.send_markdown(f"Stand by {dispatcher.user_mention()}, I'm getting the tags for {device_name}.") + dispatcher.send_markdown(f"Stand by {dispatcher.user_mention()}, I'm getting the tags for {device_name}.", ephemeral=True) device_id = get_device_id_from_hostname(device_name) tags = grpcutils.get_device_tags(device_id, PLUGIN_SETTINGS) dispatcher.send_blocks( dispatcher.command_response_header( - "cloudvision", "get-tags", [("Device Name", device_name)], "information", cloudvision_logo(dispatcher) + "cloudvision", "get-tags", [("Device Name", device_name)], "information", ) ) @@ -445,7 +450,7 @@ def get_device_cve(dispatcher, device_name=None): if device_name == "all": bug_count = get_bug_device_report() - dispatcher.send_markdown(f"Stand by {dispatcher.user_mention()}, I'm getting that CVE report for you.") + dispatcher.send_markdown(f"Stand by {dispatcher.user_mention()}, I'm getting that CVE report for you.", ephemeral=True) dispatcher.send_blocks( dispatcher.command_response_header( From b03fd978da9c613a5e4e77e43a09f2b3549e14d3 Mon Sep 17 00:00:00 2001 From: Adam Byczkowski Date: Fri, 6 Aug 2021 15:50:32 +0000 Subject: [PATCH 7/9] Added CVP_TOKEN for onprem service account --- README.md | 4 +++- nautobot_chatops_arista_cloudvision/utils.py | 7 +++++++ nautobot_chatops_arista_cloudvision/worker.py | 5 ----- 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index b21fe49..5321701 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,8 @@ PLUGINS_CONFIG = { 'cvp_username': os.getenv("CVP_USERNAME"), 'cvp_password': os.getenv("CVP_PASSWORD"), 'cvp_host': os.getenv("CVP_HOST"), - "cvp_insecure": os.getenv("CVP_INSECURE"), + 'cvp_token': os.getenv("CVP_TOKEN") + 'cvp_insecure': os.getenv("CVP_INSECURE"), 'on_prem': os.getenv("ON_PREM") } } @@ -42,6 +43,7 @@ For on premise instance of CloudVision, these environment variables must be set. - `CVP_USERNAME`: The username that will be used to authenticate to CloudVision. - `CVP_PASSWORD`: The password for the configured username. - `CVP_HOST`: The IP or hostname of the on premise CloudVision appliance. +- `CVP_TOKEN`: Token generated from the on=prem instance service account. - `CVP_INSECURE`: If this is set to `True`, the appliance cert will be downloaded and automatically trusted. Otherwise, the appliance is expected to have a valid certificate. - `ON_PREM`: By default this is set to False, this must be changed to `True` if using an on-prem instance of CloudVision. diff --git a/nautobot_chatops_arista_cloudvision/utils.py b/nautobot_chatops_arista_cloudvision/utils.py index 83db016..fc711a1 100644 --- a/nautobot_chatops_arista_cloudvision/utils.py +++ b/nautobot_chatops_arista_cloudvision/utils.py @@ -21,6 +21,7 @@ CVP_HOST = PLUGIN_SETTINGS.get("cvp_host") CVP_INSECURE = PLUGIN_SETTINGS.get("cvp_insecure") ON_PREM = PLUGIN_SETTINGS.get("on_prem") +CVP_TOKEN = PLUGIN_SETTINGS.get("cvp_token") CVP_TOKEN_PATH = f"{directory}/token.txt" CRT_FILE_PATH = f"{directory}/cvp.crt" @@ -232,6 +233,7 @@ def get_active_events_data(apiserverAddr=None, token=None, certs=None, key=None, """Gets a list of active event types from CVP.""" if check_on_prem(): apiserverAddr = CVP_HOST + token = CVP_TOKEN else: apiserverAddr = CVAAS_ADDR token = CVAAS_TOKEN @@ -262,6 +264,7 @@ def get_active_events_data_filter( """Gets a list of active event types from CVP in a specific time range.""" if check_on_prem(): apiserverAddr = CVP_HOST + token = CVP_TOKEN else: apiserverAddr = CVAAS_ADDR token = CVAAS_TOKEN @@ -310,6 +313,7 @@ def get_active_severity_types(apiserverAddr=None, token=None, certs=None, key=No # pylint: disable=invalid-name if check_on_prem(): apiserverAddr = CVP_HOST + token = CVP_TOKEN else: apiserverAddr = CVAAS_ADDR token = CVAAS_TOKEN @@ -341,6 +345,7 @@ def get_device_bugs_data(device_id, apiserverAddr=None, token=None, certs=None, # pylint: disable=invalid-name,too-many-arguments if check_on_prem(): apiserverAddr = CVP_HOST + token = CVP_TOKEN else: apiserverAddr = CVAAS_ADDR token = CVAAS_TOKEN @@ -361,6 +366,7 @@ def get_bug_info(bug_id, apiserverAddr=None, token=None): # pylint: disable=invalid-name if check_on_prem(): apiserverAddr = CVP_HOST + token = CVP_TOKEN else: apiserverAddr = CVAAS_ADDR token = CVAAS_TOKEN @@ -387,6 +393,7 @@ def get_bug_device_report(apiserverAddr=None, token=None): # pylint: disable=invalid-name if check_on_prem(): apiserverAddr = CVP_HOST + token = CVP_TOKEN else: apiserverAddr = CVAAS_ADDR token = CVAAS_TOKEN diff --git a/nautobot_chatops_arista_cloudvision/worker.py b/nautobot_chatops_arista_cloudvision/worker.py index a88204f..0909583 100644 --- a/nautobot_chatops_arista_cloudvision/worker.py +++ b/nautobot_chatops_arista_cloudvision/worker.py @@ -68,11 +68,6 @@ def cloudvision_chatbot(subcommand, **kwargs): """Interact with cloudvision.""" return handle_subcommands("cloudvision", subcommand, **kwargs) -@subcommand_of("cloudvision") -def test(dispatcher): - """Test print""" - dispatcher.send_markdown(PLUGIN_SETTINGS.get("cvp_host")) - @subcommand_of("cloudvision") def get_devices_in_container(dispatcher, container_name=None): """Get a list of devices in a Cloudvision container.""" From eec948a755fe51529734a4fe4e0b320ad6822fff Mon Sep 17 00:00:00 2001 From: Adam Byczkowski Date: Mon, 9 Aug 2021 04:08:06 +0000 Subject: [PATCH 8/9] Updated GRPC onprem client --- nautobot_chatops_arista_cloudvision/utils.py | 332 +++++++++++++------ 1 file changed, 223 insertions(+), 109 deletions(-) diff --git a/nautobot_chatops_arista_cloudvision/utils.py b/nautobot_chatops_arista_cloudvision/utils.py index fc711a1..6cf567e 100644 --- a/nautobot_chatops_arista_cloudvision/utils.py +++ b/nautobot_chatops_arista_cloudvision/utils.py @@ -7,6 +7,7 @@ from cloudvision.Connector.grpc_client import GRPCClient, create_query from cvprac.cvp_client import CvpClient from django.conf import settings +from os import path fullpath = os.path.abspath(__file__) directory = os.path.dirname(fullpath) @@ -22,8 +23,8 @@ CVP_INSECURE = PLUGIN_SETTINGS.get("cvp_insecure") ON_PREM = PLUGIN_SETTINGS.get("on_prem") CVP_TOKEN = PLUGIN_SETTINGS.get("cvp_token") -CVP_TOKEN_PATH = f"{directory}/token.txt" -CRT_FILE_PATH = f"{directory}/cvp.crt" +CVP_TOKEN_PATH = "token.txt" +CRT_FILE_PATH = "cvp.crt" CVP_LOGO_PATH = "cloudvision/CloudvisionLogoSquare.png" @@ -63,16 +64,6 @@ def connect_cvp(): if ON_PREM.lower() == "true": clnt = CvpClient() clnt.connect([CVP_HOST], CVP_USERNAME, CVP_PASSWORD) - token_request = requests.post( - f"https://{CVP_HOST}/cvpservice/login/authenticate.do", - auth=(CVP_USERNAME, CVP_PASSWORD), - verify=False, # nosec - ) - with open("cvp_token.txt", "w") as file: - file.write(token_request.json()["sessionId"]) - if CVP_INSECURE: - with open("cvp.crt", "w") as file: - file.write(ssl.get_server_certificate((CVP_HOST, 443))) else: clnt = CvpClient() clnt.connect(["www.arista.io"], username="", password="", is_cvaas=True, api_token=CVAAS_TOKEN) # nosec @@ -232,11 +223,8 @@ def get_active_events_data(apiserverAddr=None, token=None, certs=None, key=None, # pylint: disable=invalid-name """Gets a list of active event types from CVP.""" if check_on_prem(): - apiserverAddr = CVP_HOST - token = CVP_TOKEN - else: - apiserverAddr = CVAAS_ADDR - token = CVAAS_TOKEN + apiserverAddr = f"{CVP_HOST}:8443" + get_token_crt() pathElts = [ "events", @@ -244,7 +232,7 @@ def get_active_events_data(apiserverAddr=None, token=None, certs=None, key=None, ] query = [create_query([(pathElts, [])], "analytics")] events = [] - with GRPCClient(apiserverAddr, tokenValue=token, key=key, ca=ca, certs=certs) as client: + with GRPCClient(apiserverAddr, token=CVP_TOKEN_PATH, ca=CRT_FILE_PATH) as client: for batch in client.get(query): for notif in batch["notifications"]: for info in notif["updates"].values(): @@ -256,6 +244,28 @@ def get_active_events_data(apiserverAddr=None, token=None, certs=None, key=None, events.append(single_event) return events + else: + apiserverAddr = CVAAS_ADDR + token = CVAAS_TOKEN + + pathElts = [ + "events", + "activeEvents", + ] + query = [create_query([(pathElts, [])], "analytics")] + events = [] + with GRPCClient(apiserverAddr, tokenValue=token, key=key, ca=ca, certs=certs) as client: + for batch in client.get(query): + for notif in batch["notifications"]: + for info in notif["updates"].values(): + single_event = {} + single_event["title"] = info["title"] + single_event["severity"] = info["severity"] + single_event["description"] = info["description"] + single_event["deviceId"] = get_cloudvision_devices_by_sn(info["data"]["deviceId"]) + events.append(single_event) + return events + def get_active_events_data_filter( filter_type, filter_value, start_time, end_time, apiserverAddr=None, token=None, certs=None, key=None, ca=None @@ -263,73 +273,125 @@ def get_active_events_data_filter( # pylint: disable=invalid-name,too-many-arguments,too-many-locals,too-many-branches,no-member """Gets a list of active event types from CVP in a specific time range.""" if check_on_prem(): - apiserverAddr = CVP_HOST - token = CVP_TOKEN + apiserverAddr = f"{CVP_HOST}:8443" + get_token_crt() + + start = Timestamp() + if isinstance(start_time, str): + start_ts = datetime.fromisoformat(start_time) + else: + start_ts = start_time + start.FromDatetime(start_ts) + + end = Timestamp() + end_ts = datetime.fromisoformat(end_time) + end.FromDatetime(end_ts) + + pathElts = [ + "events", + "activeEvents", + ] + query = [create_query([(pathElts, [])], "analytics")] + events = [] + with GRPCClient(apiserverAddr, token=CVP_TOKEN_PATH, ca=CRT_FILE_PATH) as client: + for batch in client.get(query, start=start, end=end): + for notif in batch["notifications"]: + for info in notif["updates"].values(): + single_event = {} + single_event["title"] = info["title"] + single_event["severity"] = info["severity"] + single_event["description"] = info["description"] + single_event["deviceId"] = get_cloudvision_devices_by_sn(info["data"]["deviceId"]) + if filter_type == "severity": + if single_event["severity"] == filter_value: + events.append(single_event) + elif filter_type == "device": + if single_event["deviceId"] == filter_value: + events.append(single_event) + elif filter_type == "type": + if info["eventType"] == filter_value: + events.append(single_event) + + return events + else: apiserverAddr = CVAAS_ADDR token = CVAAS_TOKEN - start = Timestamp() - if isinstance(start_time, str): - start_ts = datetime.fromisoformat(start_time) - else: - start_ts = start_time - start.FromDatetime(start_ts) - - end = Timestamp() - end_ts = datetime.fromisoformat(end_time) - end.FromDatetime(end_ts) - - pathElts = [ - "events", - "activeEvents", - ] - query = [create_query([(pathElts, [])], "analytics")] - events = [] - with GRPCClient(apiserverAddr, tokenValue=token, key=key, ca=ca, certs=certs) as client: - for batch in client.get(query, start=start, end=end): - for notif in batch["notifications"]: - for info in notif["updates"].values(): - single_event = {} - single_event["title"] = info["title"] - single_event["severity"] = info["severity"] - single_event["description"] = info["description"] - single_event["deviceId"] = get_cloudvision_devices_by_sn(info["data"]["deviceId"]) - if filter_type == "severity": - if single_event["severity"] == filter_value: - events.append(single_event) - elif filter_type == "device": - if single_event["deviceId"] == filter_value: - events.append(single_event) - elif filter_type == "type": - if info["eventType"] == filter_value: - events.append(single_event) - - return events + start = Timestamp() + if isinstance(start_time, str): + start_ts = datetime.fromisoformat(start_time) + else: + start_ts = start_time + start.FromDatetime(start_ts) + + end = Timestamp() + end_ts = datetime.fromisoformat(end_time) + end.FromDatetime(end_ts) + + pathElts = [ + "events", + "activeEvents", + ] + query = [create_query([(pathElts, [])], "analytics")] + events = [] + with GRPCClient(apiserverAddr, tokenValue=token, key=key, ca=ca, certs=certs) as client: + for batch in client.get(query, start=start, end=end): + for notif in batch["notifications"]: + for info in notif["updates"].values(): + single_event = {} + single_event["title"] = info["title"] + single_event["severity"] = info["severity"] + single_event["description"] = info["description"] + single_event["deviceId"] = get_cloudvision_devices_by_sn(info["data"]["deviceId"]) + if filter_type == "severity": + if single_event["severity"] == filter_value: + events.append(single_event) + elif filter_type == "device": + if single_event["deviceId"] == filter_value: + events.append(single_event) + elif filter_type == "type": + if info["eventType"] == filter_value: + events.append(single_event) + + return events def get_active_severity_types(apiserverAddr=None, token=None, certs=None, key=None, ca=None): """Gets a list of active event types from CVP.""" # pylint: disable=invalid-name if check_on_prem(): - apiserverAddr = CVP_HOST - token = CVP_TOKEN + apiserverAddr = f"{CVP_HOST}:8443" + get_token_crt() + + pathElts = [ + "events", + "type", + ] + query = [create_query([(pathElts, [])], "analytics")] + event_types = [] + with GRPCClient(apiserverAddr, token=CVP_TOKEN_PATH, ca=CRT_FILE_PATH) as client: + for batch in client.get(query): + for notif in batch["notifications"]: + for info in notif["updates"]: + event_types.append(info) + return event_types else: apiserverAddr = CVAAS_ADDR token = CVAAS_TOKEN - pathElts = [ - "events", - "type", - ] - query = [create_query([(pathElts, [])], "analytics")] - event_types = [] - with GRPCClient(apiserverAddr, tokenValue=token, key=key, ca=ca, certs=certs) as client: - for batch in client.get(query): - for notif in batch["notifications"]: - for info in notif["updates"]: - event_types.append(info) - return event_types + pathElts = [ + "events", + "type", + ] + query = [create_query([(pathElts, [])], "analytics")] + event_types = [] + with GRPCClient(apiserverAddr, tokenValue=token, key=key, ca=ca, certs=certs) as client: + for batch in client.get(query): + for notif in batch["notifications"]: + for info in notif["updates"]: + event_types.append(info) + return event_types def get_applied_tags(device_id): @@ -344,70 +406,122 @@ def get_device_bugs_data(device_id, apiserverAddr=None, token=None, certs=None, """Get bugs associated with a device.""" # pylint: disable=invalid-name,too-many-arguments if check_on_prem(): - apiserverAddr = CVP_HOST - token = CVP_TOKEN + apiserverAddr = f"{CVP_HOST}:8443" + get_token_crt() + + pathElts = ["tags", "BugAlerts", "devices"] + query = [create_query([(pathElts, [])], "analytics")] + bugs = [] + with GRPCClient(apiserverAddr, token=CVP_TOKEN_PATH, ca=CRT_FILE_PATH) as client: + for batch in client.get(query): + for notif in batch["notifications"]: + if notif["updates"].get(device_id): + return notif["updates"].get(device_id) + return bugs else: apiserverAddr = CVAAS_ADDR token = CVAAS_TOKEN - pathElts = ["tags", "BugAlerts", "devices"] - query = [create_query([(pathElts, [])], "analytics")] - bugs = [] - with GRPCClient(apiserverAddr, tokenValue=token, key=key, ca=ca, certs=certs) as client: - for batch in client.get(query): - for notif in batch["notifications"]: - if notif["updates"].get(device_id): - return notif["updates"].get(device_id) - return bugs + pathElts = ["tags", "BugAlerts", "devices"] + query = [create_query([(pathElts, [])], "analytics")] + bugs = [] + with GRPCClient(apiserverAddr, tokenValue=token, key=key, ca=ca, certs=certs) as client: + for batch in client.get(query): + for notif in batch["notifications"]: + if notif["updates"].get(device_id): + return notif["updates"].get(device_id) + return bugs def get_bug_info(bug_id, apiserverAddr=None, token=None): """Get detailed information about a bug given its identifier.""" # pylint: disable=invalid-name if check_on_prem(): - apiserverAddr = CVP_HOST - token = CVP_TOKEN + apiserverAddr = f"{CVP_HOST}:8443" + get_token_crt() + + pathElts = [ + "BugAlerts", + "bugs", + int(bug_id), + ] + query = [create_query([(pathElts, [])], "analytics")] + bug_info = {} + with GRPCClient(apiserverAddr, token=CVP_TOKEN_PATH, ca=CRT_FILE_PATH) as client: + for batch in client.get(query): + for notif in batch["notifications"]: + bug_info["identifier"] = bug_id + bug_info["summary"] = notif["updates"]["alertNote"] + bug_info["severity"] = notif["updates"]["severity"] + bug_info["versions_fixed"] = notif["updates"]["versionFixed"] + return bug_info + else: apiserverAddr = CVAAS_ADDR token = CVAAS_TOKEN - pathElts = [ - "BugAlerts", - "bugs", - int(bug_id), - ] - query = [create_query([(pathElts, [])], "analytics")] - bug_info = {} - with GRPCClient(apiserverAddr, tokenValue=token) as client: - for batch in client.get(query): - for notif in batch["notifications"]: - bug_info["identifier"] = bug_id - bug_info["summary"] = notif["updates"]["alertNote"] - bug_info["severity"] = notif["updates"]["severity"] - bug_info["versions_fixed"] = notif["updates"]["versionFixed"] - return bug_info + pathElts = [ + "BugAlerts", + "bugs", + int(bug_id), + ] + query = [create_query([(pathElts, [])], "analytics")] + bug_info = {} + with GRPCClient(apiserverAddr, tokenValue=token) as client: + for batch in client.get(query): + for notif in batch["notifications"]: + bug_info["identifier"] = bug_id + bug_info["summary"] = notif["updates"]["alertNote"] + bug_info["severity"] = notif["updates"]["severity"] + bug_info["versions_fixed"] = notif["updates"]["versionFixed"] + return bug_info def get_bug_device_report(apiserverAddr=None, token=None): """Get how many bugs each device has.""" # pylint: disable=invalid-name if check_on_prem(): - apiserverAddr = CVP_HOST - token = CVP_TOKEN + apiserverAddr = f"{CVP_HOST}:8443" + get_token_crt() + + pathElts = ["BugAlerts", "DevicesBugsCount"] + query = [create_query([(pathElts, [])], "analytics")] + bug_count = {} + with GRPCClient(apiserverAddr, token=CVP_TOKEN_PATH, ca=CRT_FILE_PATH) as client: + for batch in client.get(query): + for notif in batch["notifications"]: + bug_count = notif["updates"] + return bug_count else: apiserverAddr = CVAAS_ADDR token = CVAAS_TOKEN - pathElts = ["BugAlerts", "DevicesBugsCount"] - query = [create_query([(pathElts, [])], "analytics")] - bug_count = {} - with GRPCClient(apiserverAddr, tokenValue=token) as client: - for batch in client.get(query): - for notif in batch["notifications"]: - bug_count = notif["updates"] - return bug_count + pathElts = ["BugAlerts", "DevicesBugsCount"] + query = [create_query([(pathElts, [])], "analytics")] + bug_count = {} + with GRPCClient(apiserverAddr, tokenValue=token) as client: + for batch in client.get(query): + for notif in batch["notifications"]: + bug_count = notif["updates"] + return bug_count def check_on_prem(): + """Checks environment variable 'on_prem'""" if ON_PREM.lower() == "false": return False return True + +def get_token_crt(): + """Writes cert and user token to files for GRPClient use.""" + + if CVP_INSECURE.lower() == "true": + r = requests.post(f"https://{CVP_HOST}/cvpservice/login/authenticate.do", auth=(CVP_USERNAME, CVP_PASSWORD), verify=False) + else: + r = requests.post(f"https://{CVP_HOST}/cvpservice/login/authenticate.do", auth=(CVP_USERNAME, CVP_PASSWORD)) + + with open("token.txt", "w") as tokenfile: + tokenfile.write(r.json()["sessionId"]) + + + with open("cvp.crt", "w") as cert_file: + cert_file.write(ssl.get_server_certificate((CVP_HOST, 8443))) \ No newline at end of file From 6e62ce5de4c5c1799c71a89cf68618b2588e3240 Mon Sep 17 00:00:00 2001 From: Adam Byczkowski Date: Sun, 8 Aug 2021 23:42:47 -0500 Subject: [PATCH 9/9] Linting and README update --- README.md | 21 +- nautobot_chatops_arista_cloudvision/utils.py | 257 +++++++++--------- nautobot_chatops_arista_cloudvision/worker.py | 43 ++- 3 files changed, 171 insertions(+), 150 deletions(-) diff --git a/README.md b/README.md index 5321701..b2aaeab 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,16 @@ -# Nautobot ChatOps Extension Arista +# Arista CloudVision ChatOps -An plugin for [Nautobot](https://github.com/nautobot/nautobot) [ChatOps Plugin](https://github.com/nautobot/nautobot-plugin-chatops/) +Using the [Nautobot ChatOps](https://github.com/nautobot/nautobot-plugin-chatops/) base framework, this app adds the ability to gather tag data, device configuration, devices in a specific container, task logs, configlets, device's common vulnerabilities and exposures, and device events from Arista's CloudVision using Slack, Webex Team, MS Teams, and Mattermost. +## Screenshots + +![cloudvision_get_active_events](https://user-images.githubusercontent.com/38091261/128059429-4e4dc269-2113-411b-9721-9ef281a361c5.PNG) + +![cloudvision_get_configlet](https://user-images.githubusercontent.com/38091261/128059458-d6395d63-6909-4219-9dcb-dff1801cbda2.PNG) + +![cloudvision_get_device_cve](https://user-images.githubusercontent.com/38091261/128059481-2ff60896-81e4-46ae-992b-7d179403fe8f.PNG) -## Instalation +## Installation The extension is available as a Python package in PyPI and can be installed with pip @@ -25,7 +32,6 @@ PLUGINS_CONFIG = { 'cvp_username': os.getenv("CVP_USERNAME"), 'cvp_password': os.getenv("CVP_PASSWORD"), 'cvp_host': os.getenv("CVP_HOST"), - 'cvp_token': os.getenv("CVP_TOKEN") 'cvp_insecure': os.getenv("CVP_INSECURE"), 'on_prem': os.getenv("ON_PREM") } @@ -43,7 +49,6 @@ For on premise instance of CloudVision, these environment variables must be set. - `CVP_USERNAME`: The username that will be used to authenticate to CloudVision. - `CVP_PASSWORD`: The password for the configured username. - `CVP_HOST`: The IP or hostname of the on premise CloudVision appliance. -- `CVP_TOKEN`: Token generated from the on=prem instance service account. - `CVP_INSECURE`: If this is set to `True`, the appliance cert will be downloaded and automatically trusted. Otherwise, the appliance is expected to have a valid certificate. - `ON_PREM`: By default this is set to False, this must be changed to `True` if using an on-prem instance of CloudVision. @@ -71,12 +76,6 @@ The following commands are available: - `get-tags [device-name]`: Get system or user tags assigned to a device. - `get-device-cve [device-name]`: Gets all the CVEs of the specified device. Can also specifiy the `all` parameter to get a count of CVE account for each device. -## Screenshots - -![cloudvision_get_active_events](https://user-images.githubusercontent.com/38091261/128059429-4e4dc269-2113-411b-9721-9ef281a361c5.PNG) -![cloudvision_get_configlet](https://user-images.githubusercontent.com/38091261/128059458-d6395d63-6909-4219-9dcb-dff1801cbda2.PNG) -![cloudvision_get_device_cve](https://user-images.githubusercontent.com/38091261/128059481-2ff60896-81e4-46ae-992b-7d179403fe8f.PNG) - ## Contributing Pull requests are welcomed and automatically built and tested against multiple version of Python and multiple version of Nautobot through TravisCI. diff --git a/nautobot_chatops_arista_cloudvision/utils.py b/nautobot_chatops_arista_cloudvision/utils.py index 6cf567e..6b85f13 100644 --- a/nautobot_chatops_arista_cloudvision/utils.py +++ b/nautobot_chatops_arista_cloudvision/utils.py @@ -7,7 +7,6 @@ from cloudvision.Connector.grpc_client import GRPCClient, create_query from cvprac.cvp_client import CvpClient from django.conf import settings -from os import path fullpath = os.path.abspath(__file__) directory = os.path.dirname(fullpath) @@ -23,7 +22,7 @@ CVP_INSECURE = PLUGIN_SETTINGS.get("cvp_insecure") ON_PREM = PLUGIN_SETTINGS.get("on_prem") CVP_TOKEN = PLUGIN_SETTINGS.get("cvp_token") -CVP_TOKEN_PATH = "token.txt" +CVP_TOKEN_PATH = "token.txt" # nosec CRT_FILE_PATH = "cvp.crt" @@ -226,35 +225,13 @@ def get_active_events_data(apiserverAddr=None, token=None, certs=None, key=None, apiserverAddr = f"{CVP_HOST}:8443" get_token_crt() - pathElts = [ - "events", - "activeEvents", - ] - query = [create_query([(pathElts, [])], "analytics")] - events = [] - with GRPCClient(apiserverAddr, token=CVP_TOKEN_PATH, ca=CRT_FILE_PATH) as client: - for batch in client.get(query): - for notif in batch["notifications"]: - for info in notif["updates"].values(): - single_event = {} - single_event["title"] = info["title"] - single_event["severity"] = info["severity"] - single_event["description"] = info["description"] - single_event["deviceId"] = get_cloudvision_devices_by_sn(info["data"]["deviceId"]) - events.append(single_event) - return events - - else: - apiserverAddr = CVAAS_ADDR - token = CVAAS_TOKEN - pathElts = [ "events", "activeEvents", ] query = [create_query([(pathElts, [])], "analytics")] events = [] - with GRPCClient(apiserverAddr, tokenValue=token, key=key, ca=ca, certs=certs) as client: + with GRPCClient(apiserverAddr, token=CVP_TOKEN_PATH, ca=CRT_FILE_PATH) as client: for batch in client.get(query): for notif in batch["notifications"]: for info in notif["updates"].values(): @@ -266,11 +243,32 @@ def get_active_events_data(apiserverAddr=None, token=None, certs=None, key=None, events.append(single_event) return events + apiserverAddr = CVAAS_ADDR + token = CVAAS_TOKEN + + pathElts = [ + "events", + "activeEvents", + ] + query = [create_query([(pathElts, [])], "analytics")] + events = [] + with GRPCClient(apiserverAddr, tokenValue=token, key=key, ca=ca, certs=certs) as client: + for batch in client.get(query): + for notif in batch["notifications"]: + for info in notif["updates"].values(): + single_event = {} + single_event["title"] = info["title"] + single_event["severity"] = info["severity"] + single_event["description"] = info["description"] + single_event["deviceId"] = get_cloudvision_devices_by_sn(info["data"]["deviceId"]) + events.append(single_event) + return events + def get_active_events_data_filter( filter_type, filter_value, start_time, end_time, apiserverAddr=None, token=None, certs=None, key=None, ca=None ): - # pylint: disable=invalid-name,too-many-arguments,too-many-locals,too-many-branches,no-member + # pylint: disable=invalid-name,too-many-arguments,too-many-locals,too-many-branches,no-member, too-many-statements """Gets a list of active event types from CVP in a specific time range.""" if check_on_prem(): apiserverAddr = f"{CVP_HOST}:8443" @@ -314,47 +312,46 @@ def get_active_events_data_filter( return events - else: - apiserverAddr = CVAAS_ADDR - token = CVAAS_TOKEN + apiserverAddr = CVAAS_ADDR + token = CVAAS_TOKEN - start = Timestamp() - if isinstance(start_time, str): - start_ts = datetime.fromisoformat(start_time) - else: - start_ts = start_time - start.FromDatetime(start_ts) + start = Timestamp() + if isinstance(start_time, str): + start_ts = datetime.fromisoformat(start_time) + else: + start_ts = start_time + start.FromDatetime(start_ts) - end = Timestamp() - end_ts = datetime.fromisoformat(end_time) - end.FromDatetime(end_ts) + end = Timestamp() + end_ts = datetime.fromisoformat(end_time) + end.FromDatetime(end_ts) - pathElts = [ - "events", - "activeEvents", - ] - query = [create_query([(pathElts, [])], "analytics")] - events = [] - with GRPCClient(apiserverAddr, tokenValue=token, key=key, ca=ca, certs=certs) as client: - for batch in client.get(query, start=start, end=end): - for notif in batch["notifications"]: - for info in notif["updates"].values(): - single_event = {} - single_event["title"] = info["title"] - single_event["severity"] = info["severity"] - single_event["description"] = info["description"] - single_event["deviceId"] = get_cloudvision_devices_by_sn(info["data"]["deviceId"]) - if filter_type == "severity": - if single_event["severity"] == filter_value: - events.append(single_event) - elif filter_type == "device": - if single_event["deviceId"] == filter_value: - events.append(single_event) - elif filter_type == "type": - if info["eventType"] == filter_value: - events.append(single_event) + pathElts = [ + "events", + "activeEvents", + ] + query = [create_query([(pathElts, [])], "analytics")] + events = [] + with GRPCClient(apiserverAddr, tokenValue=token, key=key, ca=ca, certs=certs) as client: + for batch in client.get(query, start=start, end=end): + for notif in batch["notifications"]: + for info in notif["updates"].values(): + single_event = {} + single_event["title"] = info["title"] + single_event["severity"] = info["severity"] + single_event["description"] = info["description"] + single_event["deviceId"] = get_cloudvision_devices_by_sn(info["data"]["deviceId"]) + if filter_type == "severity": + if single_event["severity"] == filter_value: + events.append(single_event) + elif filter_type == "device": + if single_event["deviceId"] == filter_value: + events.append(single_event) + elif filter_type == "type": + if info["eventType"] == filter_value: + events.append(single_event) - return events + return events def get_active_severity_types(apiserverAddr=None, token=None, certs=None, key=None, ca=None): @@ -376,22 +373,22 @@ def get_active_severity_types(apiserverAddr=None, token=None, certs=None, key=No for info in notif["updates"]: event_types.append(info) return event_types - else: - apiserverAddr = CVAAS_ADDR - token = CVAAS_TOKEN - pathElts = [ - "events", - "type", - ] - query = [create_query([(pathElts, [])], "analytics")] - event_types = [] - with GRPCClient(apiserverAddr, tokenValue=token, key=key, ca=ca, certs=certs) as client: - for batch in client.get(query): - for notif in batch["notifications"]: - for info in notif["updates"]: - event_types.append(info) - return event_types + apiserverAddr = CVAAS_ADDR + token = CVAAS_TOKEN + + pathElts = [ + "events", + "type", + ] + query = [create_query([(pathElts, [])], "analytics")] + event_types = [] + with GRPCClient(apiserverAddr, tokenValue=token, key=key, ca=ca, certs=certs) as client: + for batch in client.get(query): + for notif in batch["notifications"]: + for info in notif["updates"]: + event_types.append(info) + return event_types def get_applied_tags(device_id): @@ -402,6 +399,7 @@ def get_applied_tags(device_id): result = clnt.post(tag_url, data=payload) return result + def get_device_bugs_data(device_id, apiserverAddr=None, token=None, certs=None, key=None, ca=None): """Get bugs associated with a device.""" # pylint: disable=invalid-name,too-many-arguments @@ -418,19 +416,19 @@ def get_device_bugs_data(device_id, apiserverAddr=None, token=None, certs=None, if notif["updates"].get(device_id): return notif["updates"].get(device_id) return bugs - else: - apiserverAddr = CVAAS_ADDR - token = CVAAS_TOKEN - pathElts = ["tags", "BugAlerts", "devices"] - query = [create_query([(pathElts, [])], "analytics")] - bugs = [] - with GRPCClient(apiserverAddr, tokenValue=token, key=key, ca=ca, certs=certs) as client: - for batch in client.get(query): - for notif in batch["notifications"]: - if notif["updates"].get(device_id): - return notif["updates"].get(device_id) - return bugs + apiserverAddr = CVAAS_ADDR + token = CVAAS_TOKEN + + pathElts = ["tags", "BugAlerts", "devices"] + query = [create_query([(pathElts, [])], "analytics")] + bugs = [] + with GRPCClient(apiserverAddr, tokenValue=token, key=key, ca=ca, certs=certs) as client: + for batch in client.get(query): + for notif in batch["notifications"]: + if notif["updates"].get(device_id): + return notif["updates"].get(device_id) + return bugs def get_bug_info(bug_id, apiserverAddr=None, token=None): @@ -456,25 +454,24 @@ def get_bug_info(bug_id, apiserverAddr=None, token=None): bug_info["versions_fixed"] = notif["updates"]["versionFixed"] return bug_info - else: - apiserverAddr = CVAAS_ADDR - token = CVAAS_TOKEN + apiserverAddr = CVAAS_ADDR + token = CVAAS_TOKEN - pathElts = [ - "BugAlerts", - "bugs", - int(bug_id), - ] - query = [create_query([(pathElts, [])], "analytics")] - bug_info = {} - with GRPCClient(apiserverAddr, tokenValue=token) as client: - for batch in client.get(query): - for notif in batch["notifications"]: - bug_info["identifier"] = bug_id - bug_info["summary"] = notif["updates"]["alertNote"] - bug_info["severity"] = notif["updates"]["severity"] - bug_info["versions_fixed"] = notif["updates"]["versionFixed"] - return bug_info + pathElts = [ + "BugAlerts", + "bugs", + int(bug_id), + ] + query = [create_query([(pathElts, [])], "analytics")] + bug_info = {} + with GRPCClient(apiserverAddr, tokenValue=token) as client: + for batch in client.get(query): + for notif in batch["notifications"]: + bug_info["identifier"] = bug_id + bug_info["summary"] = notif["updates"]["alertNote"] + bug_info["severity"] = notif["updates"]["severity"] + bug_info["versions_fixed"] = notif["updates"]["versionFixed"] + return bug_info def get_bug_device_report(apiserverAddr=None, token=None): @@ -492,36 +489,42 @@ def get_bug_device_report(apiserverAddr=None, token=None): for notif in batch["notifications"]: bug_count = notif["updates"] return bug_count - else: - apiserverAddr = CVAAS_ADDR - token = CVAAS_TOKEN - pathElts = ["BugAlerts", "DevicesBugsCount"] - query = [create_query([(pathElts, [])], "analytics")] - bug_count = {} - with GRPCClient(apiserverAddr, tokenValue=token) as client: - for batch in client.get(query): - for notif in batch["notifications"]: - bug_count = notif["updates"] - return bug_count + apiserverAddr = CVAAS_ADDR + token = CVAAS_TOKEN + + pathElts = ["BugAlerts", "DevicesBugsCount"] + query = [create_query([(pathElts, [])], "analytics")] + bug_count = {} + with GRPCClient(apiserverAddr, tokenValue=token) as client: + for batch in client.get(query): + for notif in batch["notifications"]: + bug_count = notif["updates"] + return bug_count + def check_on_prem(): - """Checks environment variable 'on_prem'""" + """Checks environment variable 'on_prem'.""" if ON_PREM.lower() == "false": return False return True -def get_token_crt(): - """Writes cert and user token to files for GRPClient use.""" +def get_token_crt(): + """Writes cert and user token to files for onprem GRPClient use.""" if CVP_INSECURE.lower() == "true": - r = requests.post(f"https://{CVP_HOST}/cvpservice/login/authenticate.do", auth=(CVP_USERNAME, CVP_PASSWORD), verify=False) + request = requests.post( + f"https://{CVP_HOST}/cvpservice/login/authenticate.do", + auth=(CVP_USERNAME, CVP_PASSWORD), + verify=False, # nosec + ) else: - r = requests.post(f"https://{CVP_HOST}/cvpservice/login/authenticate.do", auth=(CVP_USERNAME, CVP_PASSWORD)) + request = requests.post( + f"https://{CVP_HOST}/cvpservice/login/authenticate.do", auth=(CVP_USERNAME, CVP_PASSWORD) + ) with open("token.txt", "w") as tokenfile: - tokenfile.write(r.json()["sessionId"]) - + tokenfile.write(request.json()["sessionId"]) with open("cvp.crt", "w") as cert_file: - cert_file.write(ssl.get_server_certificate((CVP_HOST, 8443))) \ No newline at end of file + cert_file.write(ssl.get_server_certificate((CVP_HOST, 8443))) diff --git a/nautobot_chatops_arista_cloudvision/worker.py b/nautobot_chatops_arista_cloudvision/worker.py index 0909583..3f2a01b 100644 --- a/nautobot_chatops_arista_cloudvision/worker.py +++ b/nautobot_chatops_arista_cloudvision/worker.py @@ -58,7 +58,9 @@ def check_credentials(dispatcher): return False else: if not PLUGIN_SETTINGS.get("cvaas_token"): - dispatcher.send_warning("Please ensure environment variable CVAAS_TOKEN is set and your nautobot config file is updated.") + dispatcher.send_warning( + "Please ensure environment variable CVAAS_TOKEN is set and your nautobot config file is updated." + ) return False return True @@ -68,6 +70,7 @@ def cloudvision_chatbot(subcommand, **kwargs): """Interact with cloudvision.""" return handle_subcommands("cloudvision", subcommand, **kwargs) + @subcommand_of("cloudvision") def get_devices_in_container(dispatcher, container_name=None): """Get a list of devices in a Cloudvision container.""" @@ -82,7 +85,8 @@ def get_devices_in_container(dispatcher, container_name=None): return False dispatcher.send_markdown( - f"Standby {dispatcher.user_mention()}, I'm getting the devices from the container {container_name}.", ephemeral=True + f"Standby {dispatcher.user_mention()}, I'm getting the devices from the container {container_name}.", + ephemeral=True, ) devices = get_cloudvision_container_devices(container_name) @@ -120,7 +124,8 @@ def get_configlet(dispatcher, configlet_name=None): return False dispatcher.send_markdown( - f"Standby {dispatcher.user_mention()}, I'm getting the configuration of the {configlet_name} configlet.", ephemeral=True + f"Standby {dispatcher.user_mention()}, I'm getting the configuration of the {configlet_name} configlet.", + ephemeral=True, ) config = get_configlet_config(configlet_name) @@ -147,7 +152,8 @@ def get_device_configuration(dispatcher, device_name=None): return False dispatcher.send_markdown( - f"Stand by {dispatcher.user_mention()}, I'm getting the running configuration for {device_name}.", ephemeral=True + f"Stand by {dispatcher.user_mention()}, I'm getting the running configuration for {device_name}.", + ephemeral=True, ) device = next(device for device in device_list if device["hostname"] == device_name) @@ -176,7 +182,9 @@ def get_task_logs(dispatcher, task_id=None): return False - dispatcher.send_markdown(f"Stand by {dispatcher.user_mention()}, I'm getting the logs of task {task_id}.", ephemeral=True) + dispatcher.send_markdown( + f"Stand by {dispatcher.user_mention()}, I'm getting the logs of task {task_id}.", ephemeral=True + ) single_task = next(task for task in task_list if task["workOrderId"] == task_id) single_task_cc_id = single_task.get("ccIdV2") @@ -244,7 +252,8 @@ def get_applied_configlets(dispatcher, filter_type=None, filter_value=None): applied_configlets = get_applied_configlets_device_id(filter_value, device_list) dispatcher.send_markdown( - f"Stand by {dispatcher.user_mention()}, I'm getting the configs applied to the {filter_type} {filter_value}.", ephemeral=True + f"Stand by {dispatcher.user_mention()}, I'm getting the configs applied to the {filter_type} {filter_value}.", + ephemeral=True, ) dispatcher.send_blocks( dispatcher.command_response_header( @@ -353,21 +362,24 @@ def get_active_events(dispatcher, filter_type=None, filter_value=None, start_tim filter_type=filter_type, filter_value=filter_value, start_time=start_time, end_time=end_time ) dispatcher.send_markdown( - f"Stand by {dispatcher.user_mention()}, I'm getting the desired events with severity level {filter_value}.", ephemeral=True + f"Stand by {dispatcher.user_mention()}, I'm getting the desired events with severity level {filter_value}.", + ephemeral=True, ) elif filter_type == "device": active_events = get_active_events_data_filter( filter_type=filter_type, filter_value=filter_value, start_time=start_time, end_time=end_time ) dispatcher.send_markdown( - f"Stand by {dispatcher.user_mention()}, I'm getting the desired events with for device {filter_value}.", ephemeral=True + f"Stand by {dispatcher.user_mention()}, I'm getting the desired events with for device {filter_value}.", + ephemeral=True, ) elif filter_type == "type": active_events = get_active_events_data_filter( filter_type=filter_type, filter_value=filter_value, start_time=start_time, end_time=end_time ) dispatcher.send_markdown( - f"Stand by {dispatcher.user_mention()}, I'm getting the desired events with for event type {filter_value}.", ephemeral=True + f"Stand by {dispatcher.user_mention()}, I'm getting the desired events with for event type {filter_value}.", + ephemeral=True, ) dispatcher.send_markdown(f"Stand by {dispatcher.user_mention()}, I'm getting those events.", ephemeral=True) @@ -408,14 +420,19 @@ def get_tags(dispatcher, device_name=None): dispatcher.prompt_from_menu("cloudvision get-tags", "Select a device.", choices) return False - dispatcher.send_markdown(f"Stand by {dispatcher.user_mention()}, I'm getting the tags for {device_name}.", ephemeral=True) + dispatcher.send_markdown( + f"Stand by {dispatcher.user_mention()}, I'm getting the tags for {device_name}.", ephemeral=True + ) device_id = get_device_id_from_hostname(device_name) tags = grpcutils.get_device_tags(device_id, PLUGIN_SETTINGS) dispatcher.send_blocks( dispatcher.command_response_header( - "cloudvision", "get-tags", [("Device Name", device_name)], "information", + "cloudvision", + "get-tags", + [("Device Name", device_name)], + "information", ) ) @@ -445,7 +462,9 @@ def get_device_cve(dispatcher, device_name=None): if device_name == "all": bug_count = get_bug_device_report() - dispatcher.send_markdown(f"Stand by {dispatcher.user_mention()}, I'm getting that CVE report for you.", ephemeral=True) + dispatcher.send_markdown( + f"Stand by {dispatcher.user_mention()}, I'm getting that CVE report for you.", ephemeral=True + ) dispatcher.send_blocks( dispatcher.command_response_header(