From 7980a2df473621a1eeea246b6b168badd75c1bb8 Mon Sep 17 00:00:00 2001 From: Stephen Kiely Date: Thu, 17 Oct 2024 16:34:34 -0500 Subject: [PATCH 01/15] Fix Upstream Testing By removing the config option for makemigrations. --- tasks.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tasks.py b/tasks.py index d660e025..745f03d1 100644 --- a/tasks.py +++ b/tasks.py @@ -374,7 +374,7 @@ def yamllint(context): @task def check_migrations(context): """Check for missing migrations.""" - command = "nautobot-server --config=nautobot/core/tests/nautobot_config.py makemigrations --dry-run --check" + command = "nautobot-server makemigrations --dry-run --check" run_command(context, command) From f46ad38de9b1b39e0d0a8a48074b9a8598e8118f Mon Sep 17 00:00:00 2001 From: Stephen Kiely Date: Thu, 17 Oct 2024 16:36:18 -0500 Subject: [PATCH 02/15] Add Change Fragment --- changes/383.housekeeping | 1 + 1 file changed, 1 insertion(+) create mode 100644 changes/383.housekeeping diff --git a/changes/383.housekeeping b/changes/383.housekeeping new file mode 100644 index 00000000..de4660f8 --- /dev/null +++ b/changes/383.housekeeping @@ -0,0 +1 @@ +Fixed `invoke check-migrations`. From 6cd5413648151a32fc453af7031f16ed18ae4ac7 Mon Sep 17 00:00:00 2001 From: Stephen Kiely Date: Thu, 17 Oct 2024 16:45:37 -0500 Subject: [PATCH 03/15] Fix RTD build errors. --- changes/383.housekeeping | 1 + docs/requirements.txt | 11 ++++++----- mkdocs.yml | 3 ++- pyproject.toml | 11 ++++++----- 4 files changed, 15 insertions(+), 11 deletions(-) diff --git a/changes/383.housekeeping b/changes/383.housekeeping index de4660f8..f137888a 100644 --- a/changes/383.housekeeping +++ b/changes/383.housekeeping @@ -1 +1,2 @@ Fixed `invoke check-migrations`. +Fixed RTD build errors. diff --git a/docs/requirements.txt b/docs/requirements.txt index bb7aa5d5..5bdbb1d5 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1,5 +1,6 @@ -mkdocs==1.3.1 -mkdocs-material==8.3.0 -mkdocstrings==0.19 -mkdocstrings-python==0.7.1 -mkdocs-version-annotations==1.0.0 +mkdocs==1.5.3 +mkdocs-material==9.1.18 +markdown-version-annotations==1.0.1 +griffe==0.31.0 +mkdocstrings-python==1.2.0 +mkdocstrings==0.22.0 diff --git a/mkdocs.yml b/mkdocs.yml index b463bf1e..e5c05c66 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -68,6 +68,8 @@ extra: link: "https://twitter.com/networktocode" name: "Network to Code Twitter" markdown_extensions: + - "markdown_version_annotations": + admonition_tag: "???" - "admonition" - "toc": permalink: true @@ -81,7 +83,6 @@ markdown_extensions: - "footnotes" plugins: - "search" - - "mkdocs-version-annotations" - "mkdocstrings": default_handler: "python" handlers: diff --git a/pyproject.toml b/pyproject.toml index dfef6fcd..3b391812 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -41,14 +41,15 @@ mysqlclient = "*" python-dotenv = "*" Markdown = "*" # Rendering docs to HTML -mkdocs = "1.3.1" +mkdocs = "1.5.3" # Material for MkDocs theme -mkdocs-material = "8.3.0" +mkdocs-material = "9.1.18" # Render custom markdown for version added/changed/remove notes -mkdocs-version-annotations = "1.0.0" +markdown-version-annotations = "1.0.1" # Automatic documentation from sources, for MkDocs -mkdocstrings = "0.19" -mkdocstrings-python = "0.7.1" +mkdocstrings = "0.22.0" +mkdocstrings-python = "1.2.0" +griffe = "0.31.0" [tool.black] line-length = 120 From 8ae66989f86e22089795471a37ce1ec9ffc20b9f Mon Sep 17 00:00:00 2001 From: Stephen Kiely Date: Thu, 17 Oct 2024 17:15:47 -0500 Subject: [PATCH 04/15] Fix docs build The *serializer_base_classes does not work with Griffe --- .gitignore | 3 + .../api/serializers.py | 66 ++++-- poetry.lock | 221 ++++++++++++++---- tasks.py | 17 +- 4 files changed, 248 insertions(+), 59 deletions(-) diff --git a/.gitignore b/.gitignore index 8e6f0a60..00488d7b 100644 --- a/.gitignore +++ b/.gitignore @@ -285,3 +285,6 @@ docker-compose.override.yml override.env invoke.yml *.dccache + +# Static docs +/nautobot_device_lifecycle_mgmt/static/nautobot_device_lifecycle_mgmt/docs/ diff --git a/nautobot_device_lifecycle_mgmt/api/serializers.py b/nautobot_device_lifecycle_mgmt/api/serializers.py index 8d4a3356..a475b488 100644 --- a/nautobot_device_lifecycle_mgmt/api/serializers.py +++ b/nautobot_device_lifecycle_mgmt/api/serializers.py @@ -35,14 +35,12 @@ NestedSoftwareLCMSerializer, ) -serializer_base_classes = [ + +class HardwareLCMSerializer( RelationshipModelSerializerMixin, TaggedObjectSerializer, CustomFieldModelSerializer, -] # pylint: disable=invalid-name - - -class HardwareLCMSerializer(*serializer_base_classes): # pylint: disable=R0901,too-few-public-methods +): # pylint: disable=R0901,too-few-public-methods """API serializer.""" url = serializers.HyperlinkedIdentityField( @@ -75,7 +73,11 @@ class Meta: # pylint: disable=too-few-public-methods ] -class ProviderLCMSerializer(*serializer_base_classes): # pylint: disable=R0901,too-few-public-methods +class ProviderLCMSerializer( + RelationshipModelSerializerMixin, + TaggedObjectSerializer, + CustomFieldModelSerializer, +): # pylint: disable=R0901,too-few-public-methods """API serializer.""" url = serializers.HyperlinkedIdentityField( @@ -102,7 +104,11 @@ class Meta: # pylint: disable=too-few-public-methods ] -class ContractLCMSerializer(*serializer_base_classes): # pylint: disable=R0901,too-few-public-methods +class ContractLCMSerializer( + RelationshipModelSerializerMixin, + TaggedObjectSerializer, + CustomFieldModelSerializer, +): # pylint: disable=R0901,too-few-public-methods """API serializer.""" url = serializers.HyperlinkedIdentityField( @@ -131,7 +137,11 @@ class Meta: # pylint: disable=too-few-public-methods ] -class ContactLCMSerializer(*serializer_base_classes): # pylint: disable=R0901,too-few-public-methods +class ContactLCMSerializer( + RelationshipModelSerializerMixin, + TaggedObjectSerializer, + CustomFieldModelSerializer, +): # pylint: disable=R0901,too-few-public-methods """API serializer.""" url = serializers.HyperlinkedIdentityField( @@ -157,7 +167,11 @@ class Meta: # pylint: disable=too-few-public-methods ] -class SoftwareLCMSerializer(*serializer_base_classes): # pylint: disable=too-few-public-methods +class SoftwareLCMSerializer( + RelationshipModelSerializerMixin, + TaggedObjectSerializer, + CustomFieldModelSerializer, +): # pylint: disable=too-few-public-methods """REST API serializer for SoftwareLCM records.""" url = serializers.HyperlinkedIdentityField( @@ -192,7 +206,11 @@ class Meta: # pylint: disable=too-few-public-methods ] -class SoftwareImageLCMSerializer(*serializer_base_classes): # pylint: disable=too-few-public-methods +class SoftwareImageLCMSerializer( + RelationshipModelSerializerMixin, + TaggedObjectSerializer, + CustomFieldModelSerializer, +): # pylint: disable=too-few-public-methods """REST API serializer for SoftwareImageLCM records.""" url = serializers.HyperlinkedIdentityField( @@ -221,7 +239,11 @@ class Meta: # pylint: disable=too-few-public-methods ] -class ValidatedSoftwareLCMSerializer(*serializer_base_classes): # pylint: disable=too-few-public-methods +class ValidatedSoftwareLCMSerializer( + RelationshipModelSerializerMixin, + TaggedObjectSerializer, + CustomFieldModelSerializer, +): # pylint: disable=too-few-public-methods """REST API serializer for ValidatedSoftwareLCM records.""" url = serializers.HyperlinkedIdentityField( @@ -251,7 +273,12 @@ class Meta: # pylint: disable=too-few-public-methods ] -class CVELCMSerializer(*serializer_base_classes, StatusModelSerializerMixin): # pylint: disable=abstract-method +class CVELCMSerializer( + RelationshipModelSerializerMixin, + TaggedObjectSerializer, + CustomFieldModelSerializer, + StatusModelSerializerMixin, +): # pylint: disable=abstract-method """REST API serializer for CVELCM records.""" url = serializers.HyperlinkedIdentityField(view_name="plugins-api:nautobot_device_lifecycle_mgmt-api:cvelcm-detail") @@ -282,7 +309,10 @@ class Meta: class VulnerabilityLCMSerializer( - *serializer_base_classes, StatusModelSerializerMixin + RelationshipModelSerializerMixin, + TaggedObjectSerializer, + CustomFieldModelSerializer, + StatusModelSerializerMixin, ): # pylint: disable=abstract-method """REST API serializer for VulnerabilityLCM records.""" @@ -321,7 +351,11 @@ class Meta: ] -class DeviceSoftwareValidationResultSerializer(*serializer_base_classes): # pylint: disable=too-few-public-methods +class DeviceSoftwareValidationResultSerializer( + RelationshipModelSerializerMixin, + TaggedObjectSerializer, + CustomFieldModelSerializer, +): # pylint: disable=too-few-public-methods """REST API serializer for DeviceSoftwareValidationResult records.""" url = serializers.HyperlinkedIdentityField( @@ -346,7 +380,9 @@ class Meta: # pylint: disable=too-few-public-methods class InventoryItemSoftwareValidationResultSerializer( - *serializer_base_classes + RelationshipModelSerializerMixin, + TaggedObjectSerializer, + CustomFieldModelSerializer, ): # pylint: disable=too-few-public-methods """REST API serializer for InventoryItemSoftwareValidationResult records.""" diff --git a/poetry.lock b/poetry.lock index f2f14e69..8b3efc1a 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.7.1 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.8.2 and should not be changed by hand. [[package]] name = "amqp" @@ -1428,13 +1428,13 @@ six = ">=1.12" [[package]] name = "griffe" -version = "0.36.2" +version = "0.31.0" description = "Signatures for entire Python programs. Extract the structure, the frame, the skeleton of your project, to generate API documentation or find breaking changes in your API." optional = false python-versions = ">=3.8" files = [ - {file = "griffe-0.36.2-py3-none-any.whl", hash = "sha256:ba71895a3f5f606b18dcd950e8a1f8e7332a37f90f24caeb002546593f2e0eee"}, - {file = "griffe-0.36.2.tar.gz", hash = "sha256:333ade7932bb9096781d83092602625dfbfe220e87a039d2801259a1bd41d1c2"}, + {file = "griffe-0.31.0-py3-none-any.whl", hash = "sha256:de6e659487497c0e73459d370f40bfda7d6f9f6cec43a687de8a110ec72a2c4f"}, + {file = "griffe-0.31.0.tar.gz", hash = "sha256:b51f6e9541ce9cb9c08580917971cd4b76b6d88e2469822d612b614fb96be776"}, ] [package.dependencies] @@ -1808,6 +1808,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" @@ -1835,6 +1849,16 @@ files = [ {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5bbe06f8eeafd38e5d0a4894ffec89378b6c6a625ff57e3028921f8ff59318ac"}, {file = "MarkupSafe-2.1.3-cp311-cp311-win32.whl", hash = "sha256:dd15ff04ffd7e05ffcb7fe79f1b98041b8ea30ae9234aed2a9168b5797c3effb"}, {file = "MarkupSafe-2.1.3-cp311-cp311-win_amd64.whl", hash = "sha256:134da1eca9ec0ae528110ccc9e48041e0828d79f24121a1a146161103c76e686"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:f698de3fd0c4e6972b92290a45bd9b1536bffe8c6759c62471efaa8acb4c37bc"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:aa57bd9cf8ae831a362185ee444e15a93ecb2e344c8e52e4d721ea3ab6ef1823"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ffcc3f7c66b5f5b7931a5aa68fc9cecc51e685ef90282f4a82f0f5e9b704ad11"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47d4f1c5f80fc62fdd7777d0d40a2e9dda0a05883ab11374334f6c4de38adffd"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1f67c7038d560d92149c060157d623c542173016c4babc0c1913cca0564b9939"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:9aad3c1755095ce347e26488214ef77e0485a3c34a50c5a5e2471dff60b9dd9c"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:14ff806850827afd6b07a5f32bd917fb7f45b046ba40c57abdb636674a8b559c"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8f9293864fe09b8149f0cc42ce56e3f0e54de883a9de90cd427f191c346eb2e1"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-win32.whl", hash = "sha256:715d3562f79d540f251b99ebd6d8baa547118974341db04f5ad06d5ea3eb8007"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-win_amd64.whl", hash = "sha256:1b8dd8c3fd14349433c79fa8abeb573a55fc0fdd769133baac1f5e07abf54aeb"}, {file = "MarkupSafe-2.1.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8e254ae696c88d98da6555f5ace2279cf7cd5b3f52be2b5cf97feafe883b58d2"}, {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cb0932dc158471523c9637e807d9bfb93e06a95cbf010f1a38b98623b929ef2b"}, {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9402b03f1a1b4dc4c19845e5c749e3ab82d5078d16a2a4c2cd2df62d57bb0707"}, @@ -1971,29 +1995,34 @@ files = [ [[package]] name = "mkdocs" -version = "1.3.1" +version = "1.5.3" description = "Project documentation with Markdown." optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" files = [ - {file = "mkdocs-1.3.1-py3-none-any.whl", hash = "sha256:fda92466393127d2da830bc6edc3a625a14b436316d1caf347690648e774c4f0"}, - {file = "mkdocs-1.3.1.tar.gz", hash = "sha256:a41a2ff25ce3bbacc953f9844ba07d106233cd76c88bac1f59cb1564ac0d87ed"}, + {file = "mkdocs-1.5.3-py3-none-any.whl", hash = "sha256:3b3a78e736b31158d64dbb2f8ba29bd46a379d0c6e324c2246c3bc3d2189cfc1"}, + {file = "mkdocs-1.5.3.tar.gz", hash = "sha256:eb7c99214dcb945313ba30426c2451b735992c73c2e10838f76d09e39ff4d0e2"}, ] [package.dependencies] -click = ">=3.3" +click = ">=7.0" +colorama = {version = ">=0.4", markers = "platform_system == \"Windows\""} ghp-import = ">=1.0" -importlib-metadata = ">=4.3" -Jinja2 = ">=2.10.2" -Markdown = ">=3.2.1,<3.4" +importlib-metadata = {version = ">=4.3", markers = "python_version < \"3.10\""} +jinja2 = ">=2.11.1" +markdown = ">=3.2.1" +markupsafe = ">=2.0.1" mergedeep = ">=1.3.4" packaging = ">=20.5" -PyYAML = ">=3.10" +pathspec = ">=0.11.1" +platformdirs = ">=2.2.0" +pyyaml = ">=5.1" pyyaml-env-tag = ">=0.1" watchdog = ">=2.0" [package.extras] i18n = ["babel (>=2.9.0)"] +min-versions = ["babel (==2.9.0)", "click (==7.0)", "colorama (==0.4)", "ghp-import (==1.0)", "importlib-metadata (==4.3)", "jinja2 (==2.11.1)", "markdown (==3.2.1)", "markupsafe (==2.0.1)", "mergedeep (==1.3.4)", "packaging (==20.5)", "pathspec (==0.11.1)", "platformdirs (==2.2.0)", "pyyaml (==5.1)", "pyyaml-env-tag (==0.1)", "typing-extensions (==3.10)", "watchdog (==2.0)"] [[package]] name = "mkdocs-autorefs" @@ -2012,22 +2041,25 @@ mkdocs = ">=1.1" [[package]] name = "mkdocs-material" -version = "8.3.0" +version = "9.1.18" description = "Documentation that simply works" optional = false python-versions = ">=3.7" files = [ - {file = "mkdocs-material-8.3.0.tar.gz", hash = "sha256:4cad98c47c49a86591ed16a790c07d59402901fc5345f575a3b7329403f5c66e"}, - {file = "mkdocs_material-8.3.0-py2.py3-none-any.whl", hash = "sha256:e37e7da1c5923c5b9f0bb8c4ad90bc694fee8f966b43b302a7ff61cb2c35c2e0"}, + {file = "mkdocs_material-9.1.18-py3-none-any.whl", hash = "sha256:5bcf8fb79ac2f253c0ffe93fa181cba87718c6438f459dc4180ac7418cc9a450"}, + {file = "mkdocs_material-9.1.18.tar.gz", hash = "sha256:981dd39979723d4cda7cfc77bbbe5e54922d5761a7af23fb8ba9edb52f114b13"}, ] [package.dependencies] -jinja2 = ">=2.11.1" +colorama = ">=0.4" +jinja2 = ">=3.0" markdown = ">=3.2" -mkdocs = ">=1.3.0" -mkdocs-material-extensions = ">=1.0.3" -pygments = ">=2.12" -pymdown-extensions = ">=9.4" +mkdocs = ">=1.4.2" +mkdocs-material-extensions = ">=1.1" +pygments = ">=2.14" +pymdown-extensions = ">=9.9.1" +regex = ">=2022.4.24" +requests = ">=2.26" [[package]] name = "mkdocs-material-extensions" @@ -2040,35 +2072,26 @@ files = [ {file = "mkdocs_material_extensions-1.1.1.tar.gz", hash = "sha256:9c003da71e2cc2493d910237448c672e00cefc800d3d6ae93d2fc69979e3bd93"}, ] -[[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.19.0" +version = "0.22.0" description = "Automatic documentation from sources, for MkDocs." optional = false python-versions = ">=3.7" files = [ - {file = "mkdocstrings-0.19.0-py3-none-any.whl", hash = "sha256:3217d510d385c961f69385a670b2677e68e07b5fea4a504d86bf54c006c87c7d"}, - {file = "mkdocstrings-0.19.0.tar.gz", hash = "sha256:efa34a67bad11229d532d89f6836a8a215937548623b64f3698a1df62e01cc3e"}, + {file = "mkdocstrings-0.22.0-py3-none-any.whl", hash = "sha256:2d4095d461554ff6a778fdabdca3c00c468c2f1459d469f7a7f622a2b23212ba"}, + {file = "mkdocstrings-0.22.0.tar.gz", hash = "sha256:82a33b94150ebb3d4b5c73bab4598c3e21468c79ec072eff6931c8f3bfc38256"}, ] [package.dependencies] +importlib-metadata = {version = ">=4.6", markers = "python_version < \"3.10\""} Jinja2 = ">=2.11.1" Markdown = ">=3.3" MarkupSafe = ">=1.1" mkdocs = ">=1.2" mkdocs-autorefs = ">=0.3.1" pymdown-extensions = ">=6.3" +typing-extensions = {version = ">=4.1", markers = "python_version < \"3.10\""} [package.extras] crystal = ["mkdocstrings-crystal (>=0.3.4)"] @@ -2077,18 +2100,18 @@ python-legacy = ["mkdocstrings-python-legacy (>=0.2.1)"] [[package]] name = "mkdocstrings-python" -version = "0.7.1" +version = "1.2.0" description = "A Python handler for mkdocstrings." optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "mkdocstrings-python-0.7.1.tar.gz", hash = "sha256:c334b382dca202dfa37071c182418a6df5818356a95d54362a2b24822ca3af71"}, - {file = "mkdocstrings_python-0.7.1-py3-none-any.whl", hash = "sha256:a22060bfa374697678e9af4e62b020d990dad2711c98f7a9fac5c0345bef93c7"}, + {file = "mkdocstrings_python-1.2.0-py3-none-any.whl", hash = "sha256:2924c9c4286fd236bdae64402b5cc5010dee13779b3edf1a65d97bc02303657b"}, + {file = "mkdocstrings_python-1.2.0.tar.gz", hash = "sha256:e16bedc236a3a6aa04c916ae6b9d37d2299c80c6e7169fc481a30ed9f8f362a4"}, ] [package.dependencies] -griffe = ">=0.11.1" -mkdocstrings = ">=0.19" +griffe = ">=0.30" +mkdocstrings = ">=0.20" [[package]] name = "mypy-extensions" @@ -2950,6 +2973,7 @@ files = [ {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938"}, {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d"}, {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515"}, + {file = "PyYAML-6.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:326c013efe8048858a6d312ddd31d56e468118ad4cdeda36c719bf5bb6192290"}, {file = "PyYAML-6.0.1-cp310-cp310-win32.whl", hash = "sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924"}, {file = "PyYAML-6.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d"}, {file = "PyYAML-6.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007"}, @@ -2957,8 +2981,16 @@ files = [ {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d"}, {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc"}, {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673"}, + {file = "PyYAML-6.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e7d73685e87afe9f3b36c799222440d6cf362062f78be1013661b00c5c6f678b"}, {file = "PyYAML-6.0.1-cp311-cp311-win32.whl", hash = "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741"}, {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"}, + {file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"}, + {file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"}, + {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a08c6f0fe150303c1c6b71ebcd7213c2858041a7e01975da3a99aed1e7a378ef"}, + {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"}, + {file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"}, + {file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"}, + {file = "PyYAML-6.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:0d3304d8c0adc42be59c5f8a4d9e3d7379e6955ad754aa9d6ab7a398b59dd1df"}, {file = "PyYAML-6.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47"}, {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98"}, {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c"}, @@ -2975,6 +3007,7 @@ files = [ {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5"}, {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696"}, {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735"}, + {file = "PyYAML-6.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:49a183be227561de579b4a36efbb21b3eab9651dd81b1858589f796549873dd6"}, {file = "PyYAML-6.0.1-cp38-cp38-win32.whl", hash = "sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206"}, {file = "PyYAML-6.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62"}, {file = "PyYAML-6.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8"}, @@ -2982,6 +3015,7 @@ files = [ {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6"}, {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0"}, {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c"}, + {file = "PyYAML-6.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:04ac92ad1925b2cff1db0cfebffb6ffc43457495c9b3c39d3fcae417d7125dc5"}, {file = "PyYAML-6.0.1-cp39-cp39-win32.whl", hash = "sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c"}, {file = "PyYAML-6.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486"}, {file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"}, @@ -3019,6 +3053,109 @@ async-timeout = {version = ">=4.0.2", markers = "python_full_version <= \"3.11.2 hiredis = ["hiredis (>=1.0.0)"] ocsp = ["cryptography (>=36.0.1)", "pyopenssl (==20.0.1)", "requests (>=2.26.0)"] +[[package]] +name = "regex" +version = "2024.9.11" +description = "Alternative regular expression module, to replace re." +optional = false +python-versions = ">=3.8" +files = [ + {file = "regex-2024.9.11-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:1494fa8725c285a81d01dc8c06b55287a1ee5e0e382d8413adc0a9197aac6408"}, + {file = "regex-2024.9.11-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0e12c481ad92d129c78f13a2a3662317e46ee7ef96c94fd332e1c29131875b7d"}, + {file = "regex-2024.9.11-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:16e13a7929791ac1216afde26f712802e3df7bf0360b32e4914dca3ab8baeea5"}, + {file = "regex-2024.9.11-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:46989629904bad940bbec2106528140a218b4a36bb3042d8406980be1941429c"}, + {file = "regex-2024.9.11-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a906ed5e47a0ce5f04b2c981af1c9acf9e8696066900bf03b9d7879a6f679fc8"}, + {file = "regex-2024.9.11-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e9a091b0550b3b0207784a7d6d0f1a00d1d1c8a11699c1a4d93db3fbefc3ad35"}, + {file = "regex-2024.9.11-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5ddcd9a179c0a6fa8add279a4444015acddcd7f232a49071ae57fa6e278f1f71"}, + {file = "regex-2024.9.11-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6b41e1adc61fa347662b09398e31ad446afadff932a24807d3ceb955ed865cc8"}, + {file = "regex-2024.9.11-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:ced479f601cd2f8ca1fd7b23925a7e0ad512a56d6e9476f79b8f381d9d37090a"}, + {file = "regex-2024.9.11-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:635a1d96665f84b292e401c3d62775851aedc31d4f8784117b3c68c4fcd4118d"}, + {file = "regex-2024.9.11-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:c0256beda696edcf7d97ef16b2a33a8e5a875affd6fa6567b54f7c577b30a137"}, + {file = "regex-2024.9.11-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:3ce4f1185db3fbde8ed8aa223fc9620f276c58de8b0d4f8cc86fd1360829edb6"}, + {file = "regex-2024.9.11-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:09d77559e80dcc9d24570da3745ab859a9cf91953062e4ab126ba9d5993688ca"}, + {file = "regex-2024.9.11-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:7a22ccefd4db3f12b526eccb129390942fe874a3a9fdbdd24cf55773a1faab1a"}, + {file = "regex-2024.9.11-cp310-cp310-win32.whl", hash = "sha256:f745ec09bc1b0bd15cfc73df6fa4f726dcc26bb16c23a03f9e3367d357eeedd0"}, + {file = "regex-2024.9.11-cp310-cp310-win_amd64.whl", hash = "sha256:01c2acb51f8a7d6494c8c5eafe3d8e06d76563d8a8a4643b37e9b2dd8a2ff623"}, + {file = "regex-2024.9.11-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:2cce2449e5927a0bf084d346da6cd5eb016b2beca10d0013ab50e3c226ffc0df"}, + {file = "regex-2024.9.11-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3b37fa423beefa44919e009745ccbf353d8c981516e807995b2bd11c2c77d268"}, + {file = "regex-2024.9.11-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:64ce2799bd75039b480cc0360907c4fb2f50022f030bf9e7a8705b636e408fad"}, + {file = "regex-2024.9.11-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a4cc92bb6db56ab0c1cbd17294e14f5e9224f0cc6521167ef388332604e92679"}, + {file = "regex-2024.9.11-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d05ac6fa06959c4172eccd99a222e1fbf17b5670c4d596cb1e5cde99600674c4"}, + {file = "regex-2024.9.11-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:040562757795eeea356394a7fb13076ad4f99d3c62ab0f8bdfb21f99a1f85664"}, + {file = "regex-2024.9.11-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6113c008a7780792efc80f9dfe10ba0cd043cbf8dc9a76ef757850f51b4edc50"}, + {file = "regex-2024.9.11-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8e5fb5f77c8745a60105403a774fe2c1759b71d3e7b4ca237a5e67ad066c7199"}, + {file = "regex-2024.9.11-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:54d9ff35d4515debf14bc27f1e3b38bfc453eff3220f5bce159642fa762fe5d4"}, + {file = "regex-2024.9.11-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:df5cbb1fbc74a8305b6065d4ade43b993be03dbe0f8b30032cced0d7740994bd"}, + {file = "regex-2024.9.11-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:7fb89ee5d106e4a7a51bce305ac4efb981536301895f7bdcf93ec92ae0d91c7f"}, + {file = "regex-2024.9.11-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:a738b937d512b30bf75995c0159c0ddf9eec0775c9d72ac0202076c72f24aa96"}, + {file = "regex-2024.9.11-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:e28f9faeb14b6f23ac55bfbbfd3643f5c7c18ede093977f1df249f73fd22c7b1"}, + {file = "regex-2024.9.11-cp311-cp311-win32.whl", hash = "sha256:18e707ce6c92d7282dfce370cd205098384b8ee21544e7cb29b8aab955b66fa9"}, + {file = "regex-2024.9.11-cp311-cp311-win_amd64.whl", hash = "sha256:313ea15e5ff2a8cbbad96ccef6be638393041b0a7863183c2d31e0c6116688cf"}, + {file = "regex-2024.9.11-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:b0d0a6c64fcc4ef9c69bd5b3b3626cc3776520a1637d8abaa62b9edc147a58f7"}, + {file = "regex-2024.9.11-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:49b0e06786ea663f933f3710a51e9385ce0cba0ea56b67107fd841a55d56a231"}, + {file = "regex-2024.9.11-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5b513b6997a0b2f10e4fd3a1313568e373926e8c252bd76c960f96fd039cd28d"}, + {file = "regex-2024.9.11-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ee439691d8c23e76f9802c42a95cfeebf9d47cf4ffd06f18489122dbb0a7ad64"}, + {file = "regex-2024.9.11-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a8f877c89719d759e52783f7fe6e1c67121076b87b40542966c02de5503ace42"}, + {file = "regex-2024.9.11-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:23b30c62d0f16827f2ae9f2bb87619bc4fba2044911e2e6c2eb1af0161cdb766"}, + {file = "regex-2024.9.11-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:85ab7824093d8f10d44330fe1e6493f756f252d145323dd17ab6b48733ff6c0a"}, + {file = "regex-2024.9.11-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8dee5b4810a89447151999428fe096977346cf2f29f4d5e29609d2e19e0199c9"}, + {file = "regex-2024.9.11-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:98eeee2f2e63edae2181c886d7911ce502e1292794f4c5ee71e60e23e8d26b5d"}, + {file = "regex-2024.9.11-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:57fdd2e0b2694ce6fc2e5ccf189789c3e2962916fb38779d3e3521ff8fe7a822"}, + {file = "regex-2024.9.11-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:d552c78411f60b1fdaafd117a1fca2f02e562e309223b9d44b7de8be451ec5e0"}, + {file = "regex-2024.9.11-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:a0b2b80321c2ed3fcf0385ec9e51a12253c50f146fddb2abbb10f033fe3d049a"}, + {file = "regex-2024.9.11-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:18406efb2f5a0e57e3a5881cd9354c1512d3bb4f5c45d96d110a66114d84d23a"}, + {file = "regex-2024.9.11-cp312-cp312-win32.whl", hash = "sha256:e464b467f1588e2c42d26814231edecbcfe77f5ac414d92cbf4e7b55b2c2a776"}, + {file = "regex-2024.9.11-cp312-cp312-win_amd64.whl", hash = "sha256:9e8719792ca63c6b8340380352c24dcb8cd7ec49dae36e963742a275dfae6009"}, + {file = "regex-2024.9.11-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:c157bb447303070f256e084668b702073db99bbb61d44f85d811025fcf38f784"}, + {file = "regex-2024.9.11-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:4db21ece84dfeefc5d8a3863f101995de646c6cb0536952c321a2650aa202c36"}, + {file = "regex-2024.9.11-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:220e92a30b426daf23bb67a7962900ed4613589bab80382be09b48896d211e92"}, + {file = "regex-2024.9.11-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eb1ae19e64c14c7ec1995f40bd932448713d3c73509e82d8cd7744dc00e29e86"}, + {file = "regex-2024.9.11-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f47cd43a5bfa48f86925fe26fbdd0a488ff15b62468abb5d2a1e092a4fb10e85"}, + {file = "regex-2024.9.11-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9d4a76b96f398697fe01117093613166e6aa8195d63f1b4ec3f21ab637632963"}, + {file = "regex-2024.9.11-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0ea51dcc0835eea2ea31d66456210a4e01a076d820e9039b04ae8d17ac11dee6"}, + {file = "regex-2024.9.11-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b7aaa315101c6567a9a45d2839322c51c8d6e81f67683d529512f5bcfb99c802"}, + {file = "regex-2024.9.11-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:c57d08ad67aba97af57a7263c2d9006d5c404d721c5f7542f077f109ec2a4a29"}, + {file = "regex-2024.9.11-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:f8404bf61298bb6f8224bb9176c1424548ee1181130818fcd2cbffddc768bed8"}, + {file = "regex-2024.9.11-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:dd4490a33eb909ef5078ab20f5f000087afa2a4daa27b4c072ccb3cb3050ad84"}, + {file = "regex-2024.9.11-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:eee9130eaad130649fd73e5cd92f60e55708952260ede70da64de420cdcad554"}, + {file = "regex-2024.9.11-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6a2644a93da36c784e546de579ec1806bfd2763ef47babc1b03d765fe560c9f8"}, + {file = "regex-2024.9.11-cp313-cp313-win32.whl", hash = "sha256:e997fd30430c57138adc06bba4c7c2968fb13d101e57dd5bb9355bf8ce3fa7e8"}, + {file = "regex-2024.9.11-cp313-cp313-win_amd64.whl", hash = "sha256:042c55879cfeb21a8adacc84ea347721d3d83a159da6acdf1116859e2427c43f"}, + {file = "regex-2024.9.11-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:35f4a6f96aa6cb3f2f7247027b07b15a374f0d5b912c0001418d1d55024d5cb4"}, + {file = "regex-2024.9.11-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:55b96e7ce3a69a8449a66984c268062fbaa0d8ae437b285428e12797baefce7e"}, + {file = "regex-2024.9.11-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:cb130fccd1a37ed894824b8c046321540263013da72745d755f2d35114b81a60"}, + {file = "regex-2024.9.11-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:323c1f04be6b2968944d730e5c2091c8c89767903ecaa135203eec4565ed2b2b"}, + {file = "regex-2024.9.11-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:be1c8ed48c4c4065ecb19d882a0ce1afe0745dfad8ce48c49586b90a55f02366"}, + {file = "regex-2024.9.11-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b5b029322e6e7b94fff16cd120ab35a253236a5f99a79fb04fda7ae71ca20ae8"}, + {file = "regex-2024.9.11-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f6fff13ef6b5f29221d6904aa816c34701462956aa72a77f1f151a8ec4f56aeb"}, + {file = "regex-2024.9.11-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:587d4af3979376652010e400accc30404e6c16b7df574048ab1f581af82065e4"}, + {file = "regex-2024.9.11-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:079400a8269544b955ffa9e31f186f01d96829110a3bf79dc338e9910f794fca"}, + {file = "regex-2024.9.11-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:f9268774428ec173654985ce55fc6caf4c6d11ade0f6f914d48ef4719eb05ebb"}, + {file = "regex-2024.9.11-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:23f9985c8784e544d53fc2930fc1ac1a7319f5d5332d228437acc9f418f2f168"}, + {file = "regex-2024.9.11-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:ae2941333154baff9838e88aa71c1d84f4438189ecc6021a12c7573728b5838e"}, + {file = "regex-2024.9.11-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:e93f1c331ca8e86fe877a48ad64e77882c0c4da0097f2212873a69bbfea95d0c"}, + {file = "regex-2024.9.11-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:846bc79ee753acf93aef4184c040d709940c9d001029ceb7b7a52747b80ed2dd"}, + {file = "regex-2024.9.11-cp38-cp38-win32.whl", hash = "sha256:c94bb0a9f1db10a1d16c00880bdebd5f9faf267273b8f5bd1878126e0fbde771"}, + {file = "regex-2024.9.11-cp38-cp38-win_amd64.whl", hash = "sha256:2b08fce89fbd45664d3df6ad93e554b6c16933ffa9d55cb7e01182baaf971508"}, + {file = "regex-2024.9.11-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:07f45f287469039ffc2c53caf6803cd506eb5f5f637f1d4acb37a738f71dd066"}, + {file = "regex-2024.9.11-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:4838e24ee015101d9f901988001038f7f0d90dc0c3b115541a1365fb439add62"}, + {file = "regex-2024.9.11-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:6edd623bae6a737f10ce853ea076f56f507fd7726bee96a41ee3d68d347e4d16"}, + {file = "regex-2024.9.11-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c69ada171c2d0e97a4b5aa78fbb835e0ffbb6b13fc5da968c09811346564f0d3"}, + {file = "regex-2024.9.11-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:02087ea0a03b4af1ed6ebab2c54d7118127fee8d71b26398e8e4b05b78963199"}, + {file = "regex-2024.9.11-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:69dee6a020693d12a3cf892aba4808fe168d2a4cef368eb9bf74f5398bfd4ee8"}, + {file = "regex-2024.9.11-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:297f54910247508e6e5cae669f2bc308985c60540a4edd1c77203ef19bfa63ca"}, + {file = "regex-2024.9.11-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ecea58b43a67b1b79805f1a0255730edaf5191ecef84dbc4cc85eb30bc8b63b9"}, + {file = "regex-2024.9.11-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:eab4bb380f15e189d1313195b062a6aa908f5bd687a0ceccd47c8211e9cf0d4a"}, + {file = "regex-2024.9.11-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:0cbff728659ce4bbf4c30b2a1be040faafaa9eca6ecde40aaff86f7889f4ab39"}, + {file = "regex-2024.9.11-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:54c4a097b8bc5bb0dfc83ae498061d53ad7b5762e00f4adaa23bee22b012e6ba"}, + {file = "regex-2024.9.11-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:73d6d2f64f4d894c96626a75578b0bf7d9e56dcda8c3d037a2118fdfe9b1c664"}, + {file = "regex-2024.9.11-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:e53b5fbab5d675aec9f0c501274c467c0f9a5d23696cfc94247e1fb56501ed89"}, + {file = "regex-2024.9.11-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:0ffbcf9221e04502fc35e54d1ce9567541979c3fdfb93d2c554f0ca583a19b35"}, + {file = "regex-2024.9.11-cp39-cp39-win32.whl", hash = "sha256:e4c22e1ac1f1ec1e09f72e6c44d8f2244173db7eb9629cc3a346a8d7ccc31142"}, + {file = "regex-2024.9.11-cp39-cp39-win_amd64.whl", hash = "sha256:faa3c142464efec496967359ca99696c896c591c56c53506bac1ad465f66e919"}, + {file = "regex-2024.9.11.tar.gz", hash = "sha256:6c188c307e8433bcb63dc1915022deb553b4203a70722fc542c363bf120a01fd"}, +] + [[package]] name = "requests" version = "2.31.0" @@ -3662,4 +3799,4 @@ testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "p [metadata] lock-version = "2.0" python-versions = ">=3.8,<3.12" -content-hash = "4af5a9f2707c56fb04681b24cdf77c5eaf0cc09206be6eaeeed0e2ee04c1ba21" +content-hash = "c4d42222a29e0628015723e0cd2a840015e8c4113e6a45aeacc7312039107c83" diff --git a/tasks.py b/tasks.py index 745f03d1..b2c98eda 100644 --- a/tasks.py +++ b/tasks.py @@ -12,7 +12,6 @@ limitations under the License. """ -from distutils.util import strtobool from invoke import Collection, task as invoke_task import os @@ -29,7 +28,14 @@ def is_truthy(arg): """ if isinstance(arg, bool): return arg - return bool(strtobool(arg)) + + val = str(arg).lower() + if val in ("y", "yes", "t", "true", "on", "1"): + return True + elif val in ("n", "no", "f", "false", "off", "0"): + return False + else: + raise ValueError(f"Invalid truthy value: `{arg}`") # Use pyinvoke configuration for default values, see http://docs.pyinvoke.org/en/stable/concepts/configuration.html @@ -379,6 +385,13 @@ def check_migrations(context): run_command(context, command) +@task() +def build_and_check_docs(context): + """Build docs for use within Nautobot.""" + command = "mkdocs build --no-directory-urls --strict" + run_command(context, command) + + @task( help={ "keepdb": "save and re-use test database between test runs for faster re-testing.", From 9710d45710440a1b2f5d858760567e4d59daa429 Mon Sep 17 00:00:00 2001 From: Stephen Kiely Date: Thu, 17 Oct 2024 20:27:23 -0500 Subject: [PATCH 05/15] Update ci.yml With latest cookiecutter-nautobot-app ltm-1.6 release. --- .github/workflows/ci.yml | 269 +++++++++++++++++++++++++-------------- 1 file changed, 176 insertions(+), 93 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e04f0b7a..2bca2790 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,186 +1,236 @@ --- name: "CI" -on: # yamllint disable-line rule:truthy - - "push" - - "pull_request" +concurrency: # Cancel any existing runs of this workflow for this same PR + group: "{% raw %}${{ github.workflow }}-${{ github.ref }}{% endraw %}" + cancel-in-progress: true +on: # yamllint disable-line rule:truthy rule:comments + push: + branches: + - "main" + - "develop" + tags: + - "v*" + pull_request: ~ env: PLUGIN_NAME: "nautobot-device-lifecycle-mgmt" jobs: black: - runs-on: "ubuntu-20.04" + runs-on: "ubuntu-22.04" env: INVOKE_NAUTOBOT_DEVICE_LIFECYCLE_MGMT_LOCAL: "True" steps: - name: "Check out repository code" - uses: "actions/checkout@v2" + uses: "actions/checkout@v4" - name: "Setup environment" - uses: "networktocode/gh-action-setup-poetry-environment@v2" + uses: "networktocode/gh-action-setup-poetry-environment@v4" - name: "Linting: black" run: "poetry run invoke black" bandit: - runs-on: "ubuntu-20.04" + runs-on: "ubuntu-22.04" env: INVOKE_NAUTOBOT_DEVICE_LIFECYCLE_MGMT_LOCAL: "True" steps: - name: "Check out repository code" - uses: "actions/checkout@v2" + uses: "actions/checkout@v4" - name: "Setup environment" - uses: "networktocode/gh-action-setup-poetry-environment@v2" + uses: "networktocode/gh-action-setup-poetry-environment@v4" - name: "Linting: bandit" run: "poetry run invoke bandit" - needs: - - "black" pydocstyle: - runs-on: "ubuntu-20.04" + runs-on: "ubuntu-22.04" env: INVOKE_NAUTOBOT_DEVICE_LIFECYCLE_MGMT_LOCAL: "True" steps: - name: "Check out repository code" - uses: "actions/checkout@v2" + uses: "actions/checkout@v4" - name: "Setup environment" - uses: "networktocode/gh-action-setup-poetry-environment@v2" + uses: "networktocode/gh-action-setup-poetry-environment@v4" - name: "Linting: pydocstyle" run: "poetry run invoke pydocstyle" - needs: - - "black" flake8: - runs-on: "ubuntu-20.04" + runs-on: "ubuntu-22.04" env: INVOKE_NAUTOBOT_DEVICE_LIFECYCLE_MGMT_LOCAL: "True" steps: - name: "Check out repository code" - uses: "actions/checkout@v2" + uses: "actions/checkout@v4" - name: "Setup environment" - uses: "networktocode/gh-action-setup-poetry-environment@v2" + uses: "networktocode/gh-action-setup-poetry-environment@v4" - name: "Linting: flake8" run: "poetry run invoke flake8" - needs: - - "black" + poetry: + runs-on: "ubuntu-22.04" + env: + INVOKE_NAUTOBOT_DEVICE_LIFECYCLE_MGMT_LOCAL: "True" + steps: + - name: "Check out repository code" + uses: "actions/checkout@v4" + - name: "Setup environment" + uses: "networktocode/gh-action-setup-poetry-environment@v4" + - name: "Checking: poetry lock file" + run: "poetry run invoke lock --check" yamllint: - runs-on: "ubuntu-20.04" + runs-on: "ubuntu-22.04" env: INVOKE_NAUTOBOT_DEVICE_LIFECYCLE_MGMT_LOCAL: "True" steps: - name: "Check out repository code" - uses: "actions/checkout@v2" + uses: "actions/checkout@v4" - name: "Setup environment" - uses: "networktocode/gh-action-setup-poetry-environment@v2" + uses: "networktocode/gh-action-setup-poetry-environment@v4" - name: "Linting: yamllint" run: "poetry run invoke yamllint" + pylint: needs: + - "bandit" + - "pydocstyle" + - "flake8" + - "poetry" + - "yamllint" - "black" - build: + runs-on: "ubuntu-22.04" strategy: fail-fast: true matrix: - python-version: ["3.8", "3.9", "3.10"] - nautobot-version: ["1.4.10", "1.6.3"] + python-version: ["3.11"] + nautobot-version: ["1.4.10"] env: - INVOKE_NAUTOBOT_DEVICE_LIFECYCLE_MGMT_PYTHON_VER: "${{ matrix.python-version }}" - INVOKE_NAUTOBOT_DEVICE_LIFECYCLE_MGMT_NAUTOBOT_VER: "${{ matrix.nautobot-version }}" - runs-on: "ubuntu-20.04" + INVOKE_NAUTOBOT_DEVICE_LIFECYCLE_MGMT_PYTHON_VER: "{% raw %}${{ matrix.python-version }}{% endraw %}" + INVOKE_NAUTOBOT_DEVICE_LIFECYCLE_MGMT_NAUTOBOT_VER: "{% raw %}${{ matrix.nautobot-version }}{% endraw %}" steps: - name: "Check out repository code" - uses: "actions/checkout@v2" + uses: "actions/checkout@v4" - name: "Setup environment" - uses: "networktocode/gh-action-setup-poetry-environment@v2" + uses: "networktocode/gh-action-setup-poetry-environment@v4" - name: "Set up Docker Buildx" id: "buildx" - uses: "docker/setup-buildx-action@v1" + uses: "docker/setup-buildx-action@v3" - name: "Build" - uses: "docker/build-push-action@v2" + uses: "docker/build-push-action@v5" with: - builder: "${{ steps.buildx.outputs.name }}" + builder: "{% raw %}${{ steps.buildx.outputs.name }}{% endraw %}" context: "./" push: false - tags: "${{ env.PLUGIN_NAME }}/nautobot:${{ matrix.nautobot-version }}-py${{ matrix.python-version }}" + load: true + tags: "{% raw %}${{ env.PLUGIN_NAME }}/nautobot:${{ matrix.nautobot-version }}-py${{ matrix.python-version }}{% endraw %}" file: "./development/Dockerfile" - cache-from: "type=gha,scope=${{ matrix.nautobot-version }}-py${{ matrix.python-version }}" - cache-to: "type=gha,scope=${{ matrix.nautobot-version }}-py${{ matrix.python-version }}" - outputs: "type=docker,dest=/tmp/${{ env.PLUGIN_NAME }}-${{ matrix.nautobot-version }}-py${{ matrix.python-version }}.tar" + cache-from: "type=gha,scope={% raw %}${{ matrix.nautobot-version }}-py${{ matrix.python-version }}{% endraw %}" + cache-to: "type=gha,scope={% raw %}${{ matrix.nautobot-version }}-py${{ matrix.python-version }}{% endraw %}" build-args: | - NAUTOBOT_VER=${{ matrix.nautobot-version }} - PYTHON_VER=${{ matrix.python-version }} - - name: "Upload the docker image" - uses: "actions/upload-artifact@v2" - with: - name: "${{ env.PLUGIN_NAME }}-${{ matrix.nautobot-version }}-py${{ matrix.python-version }}" - path: "/tmp/${{ env.PLUGIN_NAME }}-${{ matrix.nautobot-version }}-py${{ matrix.python-version }}.tar" - retention-days: 1 + NAUTOBOT_VER={% raw %}${{ matrix.nautobot-version }}{% endraw %} + PYTHON_VER={% raw %}${{ matrix.python-version }}{% endraw %} + - name: "Copy credentials" + run: "cp development/creds.example.env development/creds.env" + - name: "Linting: pylint" + run: "poetry run invoke pylint" + check-migrations: needs: - "bandit" - "pydocstyle" - "flake8" + - "poetry" - "yamllint" - pylint: + - "black" + runs-on: "ubuntu-22.04" strategy: fail-fast: true matrix: - python-version: ["3.8"] - nautobot-version: ["1.4.10"] + python-version: ["3.11"] + nautobot-version: ["1.6"] env: - INVOKE_NAUTOBOT_DEVICE_LIFECYCLE_MGMT_PYTHON_VER: "${{ matrix.python-version }}" - INVOKE_NAUTOBOT_DEVICE_LIFECYCLE_MGMT_NAUTOBOT_VER: "${{ matrix.nautobot-version }}" - runs-on: "ubuntu-20.04" + INVOKE_NAUTOBOT_DEVICE_LIFECYCLE_MGMT_PYTHON_VER: "{% raw %}${{ matrix.python-version }}{% endraw %}" + INVOKE_NAUTOBOT_DEVICE_LIFECYCLE_MGMT_NAUTOBOT_VER: "{% raw %}${{ matrix.nautobot-version }}{% endraw %}" steps: - name: "Check out repository code" - uses: "actions/checkout@v2" + uses: "actions/checkout@v4" - name: "Setup environment" - uses: "networktocode/gh-action-setup-poetry-environment@v2" - - name: "Download the docker image" - uses: "actions/download-artifact@v2" + uses: "networktocode/gh-action-setup-poetry-environment@v4" + - name: "Set up Docker Buildx" + id: "buildx" + uses: "docker/setup-buildx-action@v3" + - name: "Build" + uses: "docker/build-push-action@v5" with: - name: "${{ env.PLUGIN_NAME }}-${{ matrix.nautobot-version }}-py${{ matrix.python-version }}" - path: "/tmp" - - name: "Load the image" - run: "docker load < /tmp/${{ env.PLUGIN_NAME }}-${{ matrix.nautobot-version }}-py${{ matrix.python-version }}.tar" + builder: "{% raw %}${{ steps.buildx.outputs.name }}{% endraw %}" + context: "./" + push: false + load: true + tags: "{% raw %}${{ env.PLUGIN_NAME }}/nautobot:${{ matrix.nautobot-version }}-py${{ matrix.python-version }}{% endraw %}" + file: "./development/Dockerfile" + cache-from: "type=gha,scope={% raw %}${{ matrix.nautobot-version }}-py${{ matrix.python-version }}{% endraw %}" + cache-to: "type=gha,scope={% raw %}${{ matrix.nautobot-version }}-py${{ matrix.python-version }}{% endraw %}" + build-args: | + NAUTOBOT_VER={% raw %}${{ matrix.nautobot-version }}{% endraw %} + PYTHON_VER={% raw %}${{ matrix.python-version }}{% endraw %} - name: "Copy credentials" run: "cp development/creds.example.env development/creds.env" - - name: "Linting: pylint" - run: "poetry run invoke pylint" - needs: - - "build" + - name: "Checking: migrations" + run: "poetry run invoke check-migrations" unittest: + needs: + - "pylint" + - "check-migrations" strategy: fail-fast: true matrix: - python-version: ["3.8", "3.9", "3.10"] - nautobot-version: ["1.4.10", "1.6.3"] - runs-on: "ubuntu-20.04" + python-version: ["3.8", "3.11"] + db-backend: ["postgresql"] + nautobot-version: ["1.6"] + include: + - python-version: "3.11" + db-backend: "postgresql" + nautobot-version: "1.4.10" + - python-version: "3.11" + db-backend: "mysql" + nautobot-version: "1.6" + runs-on: "ubuntu-22.04" env: - INVOKE_NAUTOBOT_DEVICE_LIFECYCLE_MGMT_PYTHON_VER: "${{ matrix.python-version }}" - INVOKE_NAUTOBOT_DEVICE_LIFECYCLE_MGMT_NAUTOBOT_VER: "${{ matrix.nautobot-version }}" + INVOKE_NAUTOBOT_DEVICE_LIFECYCLE_MGMT_PYTHON_VER: "{% raw %}${{ matrix.python-version }}{% endraw %}" + INVOKE_NAUTOBOT_DEVICE_LIFECYCLE_MGMT_NAUTOBOT_VER: "{% raw %}${{ matrix.nautobot-version }}{% endraw %}" steps: - name: "Check out repository code" - uses: "actions/checkout@v2" + uses: "actions/checkout@v4" - name: "Setup environment" - uses: "networktocode/gh-action-setup-poetry-environment@v2" - - name: "Download the docker image" - uses: "actions/download-artifact@v2" + uses: "networktocode/gh-action-setup-poetry-environment@v4" + - name: "Set up Docker Buildx" + id: "buildx" + uses: "docker/setup-buildx-action@v3" + - name: "Build" + uses: "docker/build-push-action@v5" with: - name: "${{ env.PLUGIN_NAME }}-${{ matrix.nautobot-version }}-py${{ matrix.python-version }}" - path: "/tmp" - - name: "Load the image" - run: "docker load < /tmp/${{ env.PLUGIN_NAME }}-${{ matrix.nautobot-version }}-py${{ matrix.python-version }}.tar" + builder: "{% raw %}${{ steps.buildx.outputs.name }}{% endraw %}" + context: "./" + push: false + load: true + tags: "{% raw %}${{ env.PLUGIN_NAME }}/nautobot:${{ matrix.nautobot-version }}-py${{ matrix.python-version }}{% endraw %}" + file: "./development/Dockerfile" + cache-from: "type=gha,scope={% raw %}${{ matrix.nautobot-version }}-py${{ matrix.python-version }}{% endraw %}" + cache-to: "type=gha,scope={% raw %}${{ matrix.nautobot-version }}-py${{ matrix.python-version }}{% endraw %}" + build-args: | + NAUTOBOT_VER={% raw %}${{ matrix.nautobot-version }}{% endraw %} + PYTHON_VER={% raw %}${{ matrix.python-version }}{% endraw %} - name: "Copy credentials" run: "cp development/creds.example.env development/creds.env" + - name: "Use Mysql invoke settings when needed" + run: "cp invoke.mysql.yml invoke.yml" + if: "matrix.db-backend == 'mysql'" - name: "Run Tests" run: "poetry run invoke unittest" - needs: - - "pylint" publish_gh: + needs: + - "unittest" name: "Publish to GitHub" - runs-on: "ubuntu-20.04" + runs-on: "ubuntu-22.04" if: "startsWith(github.ref, 'refs/tags/v')" steps: - name: "Check out repository code" - uses: "actions/checkout@v2" + uses: "actions/checkout@v4" - name: "Set up Python" - uses: "actions/setup-python@v2" + uses: "actions/setup-python@v4" with: - python-version: "3.9" + python-version: "3.11" - name: "Install Python Packages" run: "pip install poetry" - name: "Set env" @@ -192,24 +242,24 @@ jobs: - name: "Upload binaries to release" uses: "svenstaro/upload-release-action@v2" with: - repo_token: "${{ secrets.GH_NAUTOBOT_BOT_TOKEN }}" + repo_token: "{% raw %}${{ secrets.NTC_GITHUB_TOKEN }}{% endraw %}" # use GH_NAUTOBOT_BOT_TOKEN for Nautobot Org repos. file: "dist/*" - tag: "${{ github.ref }}" + tag: "{% raw %}${{ github.ref }}{% endraw %}" overwrite: true file_glob: true + publish_pypi: needs: - "unittest" - publish_pypi: name: "Push Package to PyPI" - runs-on: "ubuntu-20.04" + runs-on: "ubuntu-22.04" if: "startsWith(github.ref, 'refs/tags/v')" steps: - name: "Check out repository code" - uses: "actions/checkout@v2" + uses: "actions/checkout@v4" - name: "Set up Python" - uses: "actions/setup-python@v2" + uses: "actions/setup-python@v4" with: - python-version: "3.9" + python-version: "3.11" - name: "Install Python Packages" run: "pip install poetry" - name: "Set env" @@ -222,6 +272,39 @@ jobs: uses: "pypa/gh-action-pypi-publish@release/v1" with: user: "__token__" - password: "${{ secrets.PYPI_API_TOKEN }}" + password: "{% raw %}${{ secrets.PYPI_API_TOKEN }}{% endraw %}" + slack-notify: needs: - - "unittest" + - "publish_gh" + - "publish_pypi" + runs-on: "ubuntu-22.04" + env: + SLACK_WEBHOOK_URL: "{% raw %}${{ secrets.SLACK_WEBHOOK_URL }}{% endraw %}" + SLACK_MESSAGE: >- + *NOTIFICATION: NEW-RELEASE-PUBLISHED*\n + Repository: <{% raw %}${{ github.server_url }}/${{ github.repository }}|${{ github.repository }}{% endraw %}>\n + Release: <{% raw %}${{ github.server_url }}/${{ github.repository }}/releases/tag/${{ github.ref_name }}|${{ github.ref_name }}{% endraw %}>\n + Published by: <{% raw %}${{ github.server_url }}/${{ github.actor }}|${{ github.actor }}{% endraw %}> + steps: + - name: "Send a notification to Slack" + # ENVs cannot be used directly in job.if. This is a workaround to check + # if SLACK_WEBHOOK_URL is present. + if: "env.SLACK_WEBHOOK_URL != ''" + uses: "slackapi/slack-github-action@v1" + with: + payload: | + { + "text": "{% raw %}${{ env.SLACK_MESSAGE }}{% endraw %}", + "blocks": [ + { + "type": "section", + "text": { + "type": "mrkdwn", + "text": "{% raw %}${{ env.SLACK_MESSAGE }}{% endraw %}" + } + } + ] + } + env: + SLACK_WEBHOOK_URL: "{% raw %}${{ secrets.SLACK_WEBHOOK_URL }}{% endraw %}" + SLACK_WEBHOOK_TYPE: "INCOMING_WEBHOOK" \ No newline at end of file From 6695e30f8e25d931e3bb296d7410a4c49ed91d09 Mon Sep 17 00:00:00 2001 From: Stephen Kiely Date: Thu, 17 Oct 2024 20:58:23 -0500 Subject: [PATCH 06/15] Apply LTM 1.6 baked cookie changes --- development/Dockerfile | 11 +- development/creds.example.env | 31 +- development/{dev.env => development.env} | 38 +-- development/development_mysql.env | 3 + development/docker-compose.base.yml | 46 ++- development/docker-compose.dev.yml | 46 ++- development/docker-compose.mysql.yml | 34 ++- development/docker-compose.postgres.yml | 14 +- development/docker-compose.redis.yml | 9 +- tasks.py | 356 +++++++++++++++++++++-- 10 files changed, 459 insertions(+), 129 deletions(-) rename development/{dev.env => development.env} (61%) create mode 100644 development/development_mysql.env diff --git a/development/Dockerfile b/development/Dockerfile index c33aaf1a..d355f6ec 100644 --- a/development/Dockerfile +++ b/development/Dockerfile @@ -10,7 +10,7 @@ ARG NAUTOBOT_VER="1.4" # Accepts a desired Python version as build argument, default to 3.8 -ARG PYTHON_VER="3.8" +ARG PYTHON_VER="3.11" # Retrieve published development image of Nautobot base which should include most CI dependencies FROM ghcr.io/nautobot/nautobot-dev:${NAUTOBOT_VER}-py${PYTHON_VER} @@ -20,13 +20,14 @@ ARG NAUTOBOT_ROOT=/opt/nautobot ENV prometheus_multiproc_dir=/prom_cache ENV NAUTOBOT_ROOT ${NAUTOBOT_ROOT} +ENV INVOKE_NAUTOBOT_DEVICE_LIFECYCLE_MGMT_LOCAL=true # Install Poetry manually via its installer script; # We might be using an older version of Nautobot that includes an older version of Poetry # and CI and local development may have a newer version of Poetry # Since this is only used for development and we don't ship this container, pinning Poetry back is not expressly necessary # We also don't need virtual environments in container -RUN curl -sSL https://install.python-poetry.org | python3 - && \ +RUN which poetry || curl -sSL https://install.python-poetry.org | python3 - && \ poetry config virtualenvs.create false # !!! USE CAUTION WHEN MODIFYING LINES ABOVE @@ -68,11 +69,13 @@ RUN sort poetry_freeze_base.txt poetry_freeze_all.txt | uniq -u > poetry_freeze_ # Install all local project as editable, constrained on Nautobot version, to get any additional # direct dependencies of the app -RUN pip install -c constraints.txt -e . +RUN --mount=type=cache,target="/root/.cache/pip",sharing=locked \ + pip install -c constraints.txt -e .[all] # Install any dev dependencies frozen from Poetry # Can be improved in Poetry 1.2 which allows `poetry install --only dev` -RUN pip install -c constraints.txt -r poetry_freeze_dev.txt +RUN --mount=type=cache,target="/root/.cache/pip",sharing=locked \ + pip install -c constraints.txt -r poetry_freeze_dev.txt COPY development/nautobot_config.py ${NAUTOBOT_ROOT}/nautobot_config.py # !!! USE CAUTION WHEN MODIFYING LINES ABOVE diff --git a/development/creds.example.env b/development/creds.example.env index db6ad546..c5b3d4cc 100644 --- a/development/creds.example.env +++ b/development/creds.example.env @@ -2,23 +2,26 @@ # CREDS File: Store private information. Copied to creds.env and always ignored ################################################################################ # Nautobot Configuration Secret Items -SECRET_KEY=r8OwDznj!!dci#P9ghmRfdu1Ysxm0AiPeDCQhKE+N_rClfWNj NAUTOBOT_CREATE_SUPERUSER=true -NAUTOBOT_SUPERUSER_API_TOKEN=0123456789abcdef0123456789abcdef01234567 +NAUTOBOT_DB_PASSWORD=changeme +NAUTOBOT_NAPALM_USERNAME='' +NAUTOBOT_NAPALM_PASSWORD='' +NAUTOBOT_REDIS_PASSWORD=changeme +NAUTOBOT_SECRET_KEY='changeme' +NAUTOBOT_SUPERUSER_NAME=admin +NAUTOBOT_SUPERUSER_EMAIL=admin@example.com NAUTOBOT_SUPERUSER_PASSWORD=admin +NAUTOBOT_SUPERUSER_API_TOKEN=0123456789abcdef0123456789abcdef01234567 -# Nauotbot DB Password to connect to the backend -# - NOTE: Should be the same as the selected backend (MySQL or Postgres password) -NAUTOBOT_DB_PASSWORD=notverysecurepwd +# Postgres +POSTGRES_PASSWORD=${NAUTOBOT_DB_PASSWORD} +PGPASSWORD=${NAUTOBOT_DB_PASSWORD} # MySQL Credentials -MYSQL_ROOT_PASSWORD=notverysecurepwd -MYSQL_PASSWORD=notverysecurepwd - -# Postgres Credentials -POSTGRES_PASSWORD=notverysecurepwd +MYSQL_ROOT_PASSWORD=${NAUTOBOT_DB_PASSWORD} +MYSQL_PASSWORD=${NAUTOBOT_DB_PASSWORD} -# Redis Credentials -REDIS_PASSWORD=notverysecurepwd -NAUTOBOT_REDIS_PASSWORD=${REDIS_PASSWORD} -NAUTOBOT_LOG_LEVEL=DEBUG +# Use these to override values in development.env +# NAUTOBOT_DB_HOST=localhost +# NAUTOBOT_REDIS_HOST=localhost +# NAUTOBOT_CONFIG=development/nautobot_config.py \ No newline at end of file diff --git a/development/dev.env b/development/development.env similarity index 61% rename from development/dev.env rename to development/development.env index b78ed766..caa0020f 100644 --- a/development/dev.env +++ b/development/development.env @@ -2,53 +2,39 @@ # DEV File: Store environment information. NOTE: Secrets NOT stored here! ################################################################################ # Nautobot Configuration Environment Variables -ALLOWED_HOSTS=* -BANNER_TOP="Local" -CHANGELOG_RETENTION=0 -MAX_PAGE_SIZE=0 -METRICS_ENABLED=True -NAPALM_TIMEOUT=5 -NAUTOBOT_ROOT=/opt/nautobot +NAUTOBOT_ALLOWED_HOSTS=* +NAUTOBOT_BANNER_TOP="Local" +NAUTOBOT_CHANGELOG_RETENTION=0 NAUTOBOT_DEBUG=True -NAUTOBOT_DJANGO_EXTENSIONS_ENABLED=True -NAUTOBOT_DJANGO_TOOLBAR_ENABLED=True NAUTOBOT_LOG_LEVEL=DEBUG NAUTOBOT_METRICS_ENABLED=True NAUTOBOT_NAPALM_TIMEOUT=5 NAUTOBOT_MAX_PAGE_SIZE=0 # Redis Configuration Environment Variables -REDIS_HOST=redis -NAUTOBOT_REDIS_HOST=${REDIS_HOST} +NAUTOBOT_REDIS_HOST=redis NAUTOBOT_REDIS_PORT=6379 # Uncomment NAUTOBOT_REDIS_SSL if using SSL # NAUTOBOT_REDIS_SSL=True -SUPERUSER_EMAIL=admin@example.com -SUPERUSER_NAME=admin - # Nautobot DB Connection Environment Variables -NAUTOBOT_DB_HOST=db NAUTOBOT_DB_NAME=nautobot NAUTOBOT_DB_USER=nautobot +NAUTOBOT_DB_HOST=db NAUTOBOT_DB_TIMEOUT=300 -# Uncomment the environment variables that related to your DB backend. -NAUTOBOT_DB_ENGINE=django.db.backends.postgresql -NAUTOBOT_DB_PORT=5432 -# NAUTOBOT_DB_ENGINE=django.db.backends.mysql -# NAUTOBOT_DB_PORT=3306 +# Use them to overwrite the defaults in nautobot_config.py +# NAUTOBOT_DB_ENGINE=django.db.backends.postgresql +# NAUTOBOT_DB_PORT=5432 # Needed for Postgres should match the values for Nautobot above -POSTGRES_DB=nautobot -POSTGRES_USER=nautobot -PGUSER=nautobot -POSTGRES_HOST=postgres +POSTGRES_USER=${NAUTOBOT_DB_USER} +POSTGRES_DB=${NAUTOBOT_DB_NAME} # Needed for MYSQL should match the values for Nautobot above -MYSQL_DATABASE=nautobot -MYSQL_USER=nautobot +MYSQL_USER=${NAUTOBOT_DB_USER} +MYSQL_DATABASE=${NAUTOBOT_DB_NAME} MYSQL_ROOT_HOST=% NAUTOBOT_DLM_ENABLED_METRICS = "nautobot_lcm_software_compliance_per_device_type,nautobot_lcm_software_compliance_per_inventory_item,nautobot_lcm_hw_end_of_support_per_part_number,nautobot_metrics_lcm_hw_end_of_support_site" \ No newline at end of file diff --git a/development/development_mysql.env b/development/development_mysql.env new file mode 100644 index 00000000..2c7a269e --- /dev/null +++ b/development/development_mysql.env @@ -0,0 +1,3 @@ +# Custom ENVs for Mysql +# Due to docker image limitations for Mysql, we need "root" user to create more than one database table +NAUTOBOT_DB_USER=root \ No newline at end of file diff --git a/development/docker-compose.base.yml b/development/docker-compose.base.yml index 1e035525..dba8a6e2 100644 --- a/development/docker-compose.base.yml +++ b/development/docker-compose.base.yml @@ -1,60 +1,50 @@ --- -x-nautobot-build: - &nautobot-build +x-nautobot-build: &nautobot-build build: args: NAUTOBOT_VER: "${NAUTOBOT_VER}" PYTHON_VER: "${PYTHON_VER}" context: "../" dockerfile: "development/Dockerfile" -x-nautobot-base: - &nautobot-base +x-nautobot-base: &nautobot-base image: "nautobot-device-lifecycle-mgmt/nautobot:${NAUTOBOT_VER}-py${PYTHON_VER}" env_file: - - "dev.env" + - "development.env" - "creds.env" tty: true -version: "3.4" services: nautobot: - ports: - - "0.0.0.0:8080:8080" depends_on: redis: condition: "service_started" db: condition: "service_healthy" - <<: [ *nautobot-build, *nautobot-base ] - celery_worker: + <<: + - *nautobot-base + - *nautobot-build + worker: entrypoint: - "sh" - - "-c" # this is to evaluate the $NAUTOBOT_LOG_LEVEL from the env - - "watchmedo auto-restart --directory './' --pattern '*.py' --recursive -- nautobot-server celery worker -l $$NAUTOBOT_LOG_LEVEL --events" + - "-c" # this is to evaluate the $NAUTOBOT_LOG_LEVEL from the env + - "nautobot-server celery worker -l $$NAUTOBOT_LOG_LEVEL --events" ## $$ because of docker-compose depends_on: - - "nautobot" - - "redis" + nautobot: + condition: "service_healthy" healthcheck: interval: "30s" timeout: "10s" start_period: "30s" retries: 3 - test: - [ - "CMD", - "bash", - "-c", - "nautobot-server celery inspect ping --destination celery@$$HOSTNAME" - ] + test: ["CMD", "bash", "-c", "nautobot-server celery inspect ping --destination celery@$$HOSTNAME"] ## $$ because of docker-compose <<: *nautobot-base - celery_beat: + beat: entrypoint: - "sh" - - "-c" - - "nautobot-server celery beat -l $$NAUTOBOT_LOG_LEVEL" # this is to evaluate the $NAUTOBOT_LOG_LEVEL from the env - healthcheck: - disable: true + - "-c" # this is to evaluate the $NAUTOBOT_LOG_LEVEL from the env + - "nautobot-server celery beat -l $$NAUTOBOT_LOG_LEVEL" ## $$ because of docker-compose depends_on: - "nautobot" - - "redis" - <<: *nautobot-base + healthcheck: + disable: true + <<: *nautobot-base \ No newline at end of file diff --git a/development/docker-compose.dev.yml b/development/docker-compose.dev.yml index 7bc0b08c..fed02124 100644 --- a/development/docker-compose.dev.yml +++ b/development/docker-compose.dev.yml @@ -3,23 +3,20 @@ # any override will need to include these volumes to use them. # see: https://github.com/docker/compose/issues/3729 --- -version: "3.4" services: nautobot: - command: "nautobot-server runserver 0.0.0.0:8080 --nothreading --insecure" + command: "nautobot-server runserver 0.0.0.0:8080" + ports: + - "8080:8080" volumes: - "./nautobot_config.py:/opt/nautobot/nautobot_config.py" - "../:/source" healthcheck: - disable: true - celery_worker: - volumes: - - "./nautobot_config.py:/opt/nautobot/nautobot_config.py" - - "../:/source" - celery_beat: - volumes: - - "./nautobot_config.py:/opt/nautobot/nautobot_config.py" - - "../:/source" + interval: "30s" + timeout: "10s" + start_period: "60s" + retries: 3 + test: ["CMD", "true"] # Due to layering, disable: true won't work. Instead, change the test docs: entrypoint: "mkdocs serve -v -a 0.0.0.0:8080" ports: @@ -30,3 +27,30 @@ services: healthcheck: disable: true tty: true + worker: + entrypoint: + - "sh" + - "-c" # this is to evaluate the $NAUTOBOT_LOG_LEVEL from the env + - "watchmedo auto-restart --directory './' --pattern '*.py' --recursive -- nautobot-server celery worker -l $$NAUTOBOT_LOG_LEVEL --events" ## $$ because of docker-compose + volumes: + - "./nautobot_config.py:/opt/nautobot/nautobot_config.py" + - "../:/source" + healthcheck: + test: ["CMD", "true"] # Due to layering, disable: true won't work. Instead, change the test + beat: + entrypoint: + - "sh" + - "-c" # this is to evaluate the $NAUTOBOT_LOG_LEVEL from the env + - "watchmedo auto-restart --directory './' --pattern '*.py' --recursive -- nautobot-server celery beat -l $$NAUTOBOT_LOG_LEVEL" ## $$ because of docker-compose + volumes: + - "./nautobot_config.py:/opt/nautobot/nautobot_config.py" + - "../:/source" + healthcheck: + test: ["CMD", "true"] # Due to layering, disable: true won't work. Instead, change the test +# To expose postgres or redis to the host uncomment the following +# postgres: +# ports: +# - "5432:5432" +# redis: +# ports: +# - "6379:6379" diff --git a/development/docker-compose.mysql.yml b/development/docker-compose.mysql.yml index 89c51f7d..dbe31cba 100644 --- a/development/docker-compose.mysql.yml +++ b/development/docker-compose.mysql.yml @@ -1,21 +1,37 @@ --- -version: "3.4" - services: + nautobot: + environment: + - "NAUTOBOT_DB_ENGINE=django.db.backends.mysql" + env_file: + - "development.env" + - "creds.env" + - "development_mysql.env" + worker: + environment: + - "NAUTOBOT_DB_ENGINE=django.db.backends.mysql" + env_file: + - "development.env" + - "creds.env" + - "development_mysql.env" db: image: "mysql:8" command: - - "--default-authentication-plugin=mysql_native_password" + - "--max_connections=1000" env_file: - - "dev.env" + - "development.env" - "creds.env" + - "development_mysql.env" volumes: - - "lcm_mysql_data:/var/lib/mysql" - ports: - - "3306:3306" + - "mysql_data:/var/lib/mysql" healthcheck: - test: ["CMD", "mysqladmin", "ping", "-h", "localhost"] + test: + - "CMD" + - "mysqladmin" + - "ping" + - "-h" + - "localhost" timeout: "20s" retries: 10 volumes: - lcm_mysql_data: # yamllint disable-line rule:empty-values + mysql_data: {} diff --git a/development/docker-compose.postgres.yml b/development/docker-compose.postgres.yml index 14d58fbb..8d96fdba 100644 --- a/development/docker-compose.postgres.yml +++ b/development/docker-compose.postgres.yml @@ -1,18 +1,20 @@ --- -version: "3.4" - services: + nautobot: + environment: + - "NAUTOBOT_DB_ENGINE=django.db.backends.postgresql" db: image: "postgres:13-alpine" + command: + - "-c" + - "max_connections=200" env_file: - - "dev.env" + - "development.env" - "creds.env" volumes: - "postgres_data:/var/lib/postgresql/data" - ports: - - "127.0.0.1:5432:5432" healthcheck: - test: ["CMD-SHELL", "pg_isready"] + test: "pg_isready --username=$$POSTGRES_USER --dbname=$$POSTGRES_DB" interval: "10s" timeout: "5s" retries: 10 diff --git a/development/docker-compose.redis.yml b/development/docker-compose.redis.yml index e46be29c..b5e266a3 100644 --- a/development/docker-compose.redis.yml +++ b/development/docker-compose.redis.yml @@ -1,14 +1,11 @@ --- -version: "3.4" services: redis: image: "redis:6-alpine" command: - "sh" - - "-c" # this is to evaluate the $REDIS_PASSWORD from the env - - "redis-server --appendonly yes --requirepass $$REDIS_PASSWORD" + - "-c" # this is to evaluate the $NAUTOBOT_REDIS_PASSWORD from the env + - "redis-server --appendonly yes --requirepass $$NAUTOBOT_REDIS_PASSWORD" env_file: - - "dev.env" + - "development.env" - "creds.env" - ports: - - "6379:6379" diff --git a/tasks.py b/tasks.py index b2c98eda..61228961 100644 --- a/tasks.py +++ b/tasks.py @@ -1,6 +1,6 @@ """Tasks for use with Invoke. -(c) 2020-2021 Network To Code +Copyright (c) 2023, 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 @@ -12,8 +12,12 @@ limitations under the License. """ -from invoke import Collection, task as invoke_task import os +from pathlib import Path +from time import sleep + +from invoke.collection import Collection +from invoke.tasks import task as invoke_task def is_truthy(arg): @@ -47,7 +51,7 @@ def is_truthy(arg): "nautobot_device_lifecycle_mgmt": { "nautobot_ver": "1.6.26", "project_name": "nautobot_device_lifecycle_mgmt", - "python_ver": "3.8", + "python_ver": "3.11", "local": False, "compose_dir": os.path.join(os.path.dirname(__file__), "development"), "compose_files": [ @@ -62,6 +66,29 @@ def is_truthy(arg): ) +def _is_compose_included(context, name): + return f"docker-compose.{name}.yml" in context.{{cookiecutter.plugin_name}}.compose_files + + +def _await_healthy_service(context, service): + container_id = docker_compose(context, f"ps -q -- {service}", pty=False, echo=False, hide=True).stdout.strip() + _await_healthy_container(context, container_id) + + +def _await_healthy_container(context, container_id): + while True: + result = context.run( + "docker inspect --format='{% raw %}{{.State.Health.Status}}{% endraw %}' " + container_id, + pty=False, + echo=False, + hide=True, + ) + if result.stdout.strip() == "healthy": + break + print(f"Waiting for `{container_id}` container to become healthy ...") + sleep(1) + + def task(function=None, *args, **kwargs): """Task decorator to override the default Invoke task decorator and add each task to the invoke namespace.""" @@ -150,6 +177,19 @@ def generate_packages(context): run_command(context, command) +@task( + help={ + "check": ( + "If enabled, check for outdated dependencies in the poetry.lock file, " + "instead of generating a new one. (default: disabled)" + ) + } +) +def lock(context, check=False): + """Generate poetry.lock inside the Nautobot container.""" + run_command(context, f"poetry {'check' if check else 'lock --no-update'}") + + # ------------------------------------------------------------------------------ # START / STOP / DEBUG # ------------------------------------------------------------------------------ @@ -181,11 +221,64 @@ def stop(context): docker_compose(context, "down") -@task -def destroy(context): +@task( + aliases=("down",), + help={ + "volumes": "Remove Docker compose volumes (default: True)", + "import-db-file": "Import database from `import-db-file` file into the fresh environment (default: empty)", + }, +) +def destroy(context, volumes=True, import_db_file=""): """Destroy all containers and volumes.""" print("Destroying Nautobot...") - docker_compose(context, "down --volumes") + docker_compose(context, f"down --remove-orphans {'--volumes' if volumes else ''}") + + if not import_db_file: + return + + if not volumes: + raise ValueError("Cannot specify `--no-volumes` and `--import-db-file` arguments at the same time.") + + print(f"Importing database file: {import_db_file}...") + + input_path = Path(import_db_file).absolute() + if not input_path.is_file(): + raise ValueError(f"File not found: {input_path}") + + command = [ + "run", + "--rm", + "--detach", + f"--volume='{input_path}:/docker-entrypoint-initdb.d/dump.sql'", + "--", + "db", + ] + + container_id = docker_compose(context, " ".join(command), pty=False, echo=False, hide=True).stdout.strip() + _await_healthy_container(context, container_id) + print("Stopping database container...") + context.run(f"docker stop {container_id}", pty=False, echo=False, hide=True) + + print("Database import complete, you can start Nautobot with the following command:") + print("invoke start") + + +@task +def export(context): + """Export docker compose configuration to `compose.yaml` file. + + Useful to: + + - Debug docker compose configuration. + - Allow using `docker compose` command directly without invoke. + """ + docker_compose(context, "convert > compose.yaml") + + +@task(name="ps", help={"all": "Show all, including stopped containers"}) +def ps_task(context, all=False): + """List containers.""" + docker_compose(context, f"ps {'--all' if all else ''}") @task @@ -219,11 +312,15 @@ def logs(context, service="nautobot", follow=False, tail=None): # ------------------------------------------------------------------------------ # ACTIONS # ------------------------------------------------------------------------------ -@task -def nbshell(context): +@task(help={"file": "Python file to execute"}) +def nbshell(context, file=""): """Launch an interactive nbshell session.""" - command = "nautobot-server nbshell" - run_command(context, command) + command = [ + "nautobot-server", + "nbshell", + f"< '{file}'" if file else "", + ] + run_command(context, " ".join(command), pty=not bool(file)) @task @@ -293,6 +390,179 @@ def post_upgrade(context): run_command(context, command) +@task( + help={ + "service": "Docker compose service name to run command in (default: nautobot).", + "command": "Command to run (default: bash).", + "file": "File to run command with (default: empty)", + }, +) +def exec(context, service="nautobot", command="bash", file=""): + """Launch a command inside the running container (defaults to bash shell inside nautobot container).""" + command = [ + "exec", + "--", + service, + command, + f"< '{file}'" if file else "", + ] + docker_compose(context, " ".join(command), pty=not bool(file)) + + +@task( + help={ + "db-name": "Database name (default: Nautobot database)", + "input-file": "SQL file to execute and quit (default: empty, start interactive CLI)", + "output-file": "Ouput file, overwrite if exists (default: empty, output to stdout)", + "query": "SQL command to execute and quit (default: empty)", + } +) +def dbshell(context, db_name="", input_file="", output_file="", query=""): + """Start database CLI inside the running `db` container. + + Doesn't use `nautobot-server dbshell`, using started `db` service container only. + """ + if input_file and query: + raise ValueError("Cannot specify both, `input_file` and `query` arguments") + if output_file and not (input_file or query): + raise ValueError("`output_file` argument requires `input_file` or `query` argument") + + env = {} + if query: + env["_SQL_QUERY"] = query + + command = [ + "exec", + "--env=_SQL_QUERY" if query else "", + "-- db sh -c '", + ] + + if _is_compose_included(context, "mysql"): + command += [ + "mysql", + "--user=$MYSQL_USER", + "--password=$MYSQL_PASSWORD", + f"--database={db_name or '$MYSQL_DATABASE'}", + ] + elif _is_compose_included(context, "postgres"): + command += [ + "psql", + "--username=$POSTGRES_USER", + f"--dbname={db_name or '$POSTGRES_DB'}", + ] + else: + raise ValueError("Unsupported database backend.") + + command += [ + "'", + '<<<"$_SQL_QUERY"' if query else "", + f"< '{input_file}'" if input_file else "", + f"> '{output_file}'" if output_file else "", + ] + + docker_compose(context, " ".join(command), env=env, pty=not (input_file or output_file or query)) + + +@task( + help={ + "db-name": "Database name to create (default: Nautobot database)", + "input-file": "SQL dump file to replace the existing database with. This can be generated using `invoke backup-db` (default: `dump.sql`).", + } +) +def import_db(context, db_name="", input_file="dump.sql"): + """Stop Nautobot containers and replace the current database with the dump into `db` container.""" + docker_compose(context, "stop -- nautobot worker beat") + start(context, "db") + _await_healthy_service(context, "db") + + command = ["exec -- db sh -c '"] + + if _is_compose_included(context, "mysql"): + if not db_name: + db_name = "$MYSQL_DATABASE" + command += [ + "mysql --user root --password=$MYSQL_ROOT_PASSWORD", + '--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;", + '"', + "&&", + "mysql", + f"--database={db_name}", + "--user=$MYSQL_USER", + "--password=$MYSQL_PASSWORD", + ] + elif _is_compose_included(context, "postgres"): + if not db_name: + db_name = "$POSTGRES_DB" + command += [ + f"dropdb --if-exists --user=$POSTGRES_USER {db_name} &&", + f"createdb --user=$POSTGRES_USER {db_name} &&", + f"psql --user=$POSTGRES_USER --dbname={db_name}", + ] + else: + raise ValueError("Unsupported database backend.") + + command += [ + "'", + f"< '{input_file}'", + ] + + docker_compose(context, " ".join(command), pty=False) + + print("Database import complete, you can start Nautobot now: `invoke start`") + + +@task( + help={ + "db-name": "Database name to backup (default: Nautobot database)", + "output-file": "Ouput file, overwrite if exists (default: `dump.sql`)", + "readable": "Flag to dump database data in more readable format (default: `True`)", + } +) +def backup_db(context, db_name="", output_file="dump.sql", readable=True): + """Dump database into `output_file` file from `db` container.""" + start(context, "db") + _await_healthy_service(context, "db") + + command = ["exec -- db sh -c '"] + + if _is_compose_included(context, "mysql"): + command += [ + "mysqldump", + "--user=root", + "--password=$MYSQL_ROOT_PASSWORD", + "--skip-extended-insert" if readable else "", + db_name if db_name else "$MYSQL_DATABASE", + ] + elif _is_compose_included(context, "postgres"): + command += [ + "pg_dump", + "--username=$POSTGRES_USER", + f"--dbname={db_name or '$POSTGRES_DB'}", + "--inserts" if readable else "", + ] + else: + raise ValueError("Unsupported database backend.") + + command += [ + "'", + f"> '{output_file}'", + ] + + docker_compose(context, " ".join(command), pty=False) + + print(50 * "=") + print("The database backup has been successfully completed and saved to the following file:") + print(output_file) + print("You can import this database backup with the following command:") + print(f"invoke import-db --input-file '{output_file}'") + print(50 * "=") + + # ------------------------------------------------------------------------------ # DOCS # ------------------------------------------------------------------------------ @@ -302,10 +572,29 @@ def docs(context): command = "mkdocs serve -v" if is_truthy(context.nautobot_device_lifecycle_mgmt.local): - print("Serving Documentation...") + print(">>> Serving Documentation at http://localhost:8001") run_command(context, command) else: - print("Only used when developing locally (i.e. context.nautobot_device_lifecycle_mgmt.local=True)!") + start(context, service="docs") + + +@task +def build_and_check_docs(context): + """Build documentation to be available within Nautobot.""" + command = "mkdocs build --no-directory-urls --strict" + run_command(context, command) + + +@task(name="help") +def help_task(context): + """Print the help of available tasks.""" + import tasks # pylint: disable=all + + root = Collection.from_module(tasks) + for task_name in sorted(root.task_names): + print(50 * "-") + print(f"invoke {task_name} --help") + context.run(f"invoke {task_name} --help") # ------------------------------------------------------------------------------ @@ -385,22 +674,25 @@ def check_migrations(context): run_command(context, command) -@task() -def build_and_check_docs(context): - """Build docs for use within Nautobot.""" - command = "mkdocs build --no-directory-urls --strict" - run_command(context, command) - - @task( help={ "keepdb": "save and re-use test database between test runs for faster re-testing.", "label": "specify a directory or module to test instead of running all Nautobot tests", "failfast": "fail as soon as a single test fails don't run the entire test suite", "buffer": "Discard output from passing tests", + "pattern": "Run specific test methods, classes, or modules instead of all tests", + "verbose": "Enable verbose test output.", } ) -def unittest(context, keepdb=False, label="nautobot_device_lifecycle_mgmt", failfast=False, buffer=True): +def unittest( + context, + keepdb=False, + label="nautobot_device_lifecycle_mgmt", + failfast=False, + buffer=True, + pattern="", + verbose=False, +): """Run Nautobot unit tests.""" command = f"coverage run --module nautobot.core.cli test {label}" @@ -410,6 +702,11 @@ def unittest(context, keepdb=False, label="nautobot_device_lifecycle_mgmt", fail command += " --failfast" if buffer: command += " --buffer" + if pattern: + command += f" -k='{pattern}'" + if verbose: + command += " --verbosity 2" + run_command(context, command) @@ -423,10 +720,12 @@ def unittest_coverage(context): @task( help={ - "failfast": "fail as soon as a single test fails don't run the entire test suite", + "failfast": "fail as soon as a single test fails don't run the entire test suite. (default: False)", + "keepdb": "Save and re-use test database between test runs for faster re-testing. (default: False)", + "lint-only": "Only run linters; unit tests will be excluded. (default: False)", } ) -def tests(context, failfast=False): +def tests(context, failfast=False, keepdb=False, lint_only=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_device_lifecycle_mgmt.local): @@ -443,9 +742,16 @@ def tests(context, failfast=False): pydocstyle(context) print("Running yamllint...") yamllint(context) + print("Running poetry check...") + lock(context, check=True) + print("Running migrations check...") + check_migrations(context) print("Running pylint...") pylint(context) - print("Running unit tests...") - unittest(context, failfast=failfast) + print("Running mkdocs...") + build_and_check_docs(context) + if not lint_only: + print("Running unit tests...") + unittest(context, failfast=failfast, keepdb=keepdb) + unittest_coverage(context) print("All tests have passed!") - unittest_coverage(context) From c2b417eb26b323a74214f896cddcd4f6369eece2 Mon Sep 17 00:00:00 2001 From: Stephen Kiely Date: Thu, 17 Oct 2024 21:09:10 -0500 Subject: [PATCH 07/15] Update tasks.py --- tasks.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tasks.py b/tasks.py index 61228961..0dece809 100644 --- a/tasks.py +++ b/tasks.py @@ -67,7 +67,7 @@ def is_truthy(arg): def _is_compose_included(context, name): - return f"docker-compose.{name}.yml" in context.{{cookiecutter.plugin_name}}.compose_files + return f"docker-compose.{name}.yml" in context.nautobot_device_lifecycle_mgmt.compose_files def _await_healthy_service(context, service): From 494ddef8002f3e7442c7163c5698fafdfa0842e9 Mon Sep 17 00:00:00 2001 From: Stephen Kiely Date: Thu, 17 Oct 2024 21:22:53 -0500 Subject: [PATCH 08/15] New lines at end of files --- .github/workflows/ci.yml | 2 +- development/docker-compose.base.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2bca2790..fb127dff 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -307,4 +307,4 @@ jobs: } env: SLACK_WEBHOOK_URL: "{% raw %}${{ secrets.SLACK_WEBHOOK_URL }}{% endraw %}" - SLACK_WEBHOOK_TYPE: "INCOMING_WEBHOOK" \ No newline at end of file + SLACK_WEBHOOK_TYPE: "INCOMING_WEBHOOK" diff --git a/development/docker-compose.base.yml b/development/docker-compose.base.yml index dba8a6e2..8ae7de49 100644 --- a/development/docker-compose.base.yml +++ b/development/docker-compose.base.yml @@ -47,4 +47,4 @@ services: - "nautobot" healthcheck: disable: true - <<: *nautobot-base \ No newline at end of file + <<: *nautobot-base From 8099de760c35581454ddbfa7d03f1b778239fc79 Mon Sep 17 00:00:00 2001 From: Stephen Kiely Date: Thu, 17 Oct 2024 21:29:35 -0500 Subject: [PATCH 09/15] Remove raw designations. --- .github/workflows/ci.yml | 70 ++++++++++++++++++++-------------------- tasks.py | 2 +- 2 files changed, 36 insertions(+), 36 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index fb127dff..c905215f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,7 +1,7 @@ --- name: "CI" concurrency: # Cancel any existing runs of this workflow for this same PR - group: "{% raw %}${{ github.workflow }}-${{ github.ref }}{% endraw %}" + group: "${{ github.workflow }}-${{ github.ref }}" cancel-in-progress: true on: # yamllint disable-line rule:truthy rule:comments push: @@ -97,8 +97,8 @@ jobs: python-version: ["3.11"] nautobot-version: ["1.4.10"] env: - INVOKE_NAUTOBOT_DEVICE_LIFECYCLE_MGMT_PYTHON_VER: "{% raw %}${{ matrix.python-version }}{% endraw %}" - INVOKE_NAUTOBOT_DEVICE_LIFECYCLE_MGMT_NAUTOBOT_VER: "{% raw %}${{ matrix.nautobot-version }}{% endraw %}" + INVOKE_NAUTOBOT_DEVICE_LIFECYCLE_MGMT_PYTHON_VER: "${{ matrix.python-version }}" + INVOKE_NAUTOBOT_DEVICE_LIFECYCLE_MGMT_NAUTOBOT_VER: "${{ matrix.nautobot-version }}" steps: - name: "Check out repository code" uses: "actions/checkout@v4" @@ -110,17 +110,17 @@ jobs: - name: "Build" uses: "docker/build-push-action@v5" with: - builder: "{% raw %}${{ steps.buildx.outputs.name }}{% endraw %}" + builder: "${{ steps.buildx.outputs.name }}" context: "./" push: false load: true - tags: "{% raw %}${{ env.PLUGIN_NAME }}/nautobot:${{ matrix.nautobot-version }}-py${{ matrix.python-version }}{% endraw %}" + tags: "${{ env.PLUGIN_NAME }}/nautobot:${{ matrix.nautobot-version }}-py${{ matrix.python-version }}" file: "./development/Dockerfile" - cache-from: "type=gha,scope={% raw %}${{ matrix.nautobot-version }}-py${{ matrix.python-version }}{% endraw %}" - cache-to: "type=gha,scope={% raw %}${{ matrix.nautobot-version }}-py${{ matrix.python-version }}{% endraw %}" + cache-from: "type=gha,scope=${{ matrix.nautobot-version }}-py${{ matrix.python-version }}" + cache-to: "type=gha,scope=${{ matrix.nautobot-version }}-py${{ matrix.python-version }}" build-args: | - NAUTOBOT_VER={% raw %}${{ matrix.nautobot-version }}{% endraw %} - PYTHON_VER={% raw %}${{ matrix.python-version }}{% endraw %} + NAUTOBOT_VER=${{ matrix.nautobot-version }} + PYTHON_VER=${{ matrix.python-version }} - name: "Copy credentials" run: "cp development/creds.example.env development/creds.env" - name: "Linting: pylint" @@ -140,8 +140,8 @@ jobs: python-version: ["3.11"] nautobot-version: ["1.6"] env: - INVOKE_NAUTOBOT_DEVICE_LIFECYCLE_MGMT_PYTHON_VER: "{% raw %}${{ matrix.python-version }}{% endraw %}" - INVOKE_NAUTOBOT_DEVICE_LIFECYCLE_MGMT_NAUTOBOT_VER: "{% raw %}${{ matrix.nautobot-version }}{% endraw %}" + INVOKE_NAUTOBOT_DEVICE_LIFECYCLE_MGMT_PYTHON_VER: "${{ matrix.python-version }}" + INVOKE_NAUTOBOT_DEVICE_LIFECYCLE_MGMT_NAUTOBOT_VER: "${{ matrix.nautobot-version }}" steps: - name: "Check out repository code" uses: "actions/checkout@v4" @@ -153,17 +153,17 @@ jobs: - name: "Build" uses: "docker/build-push-action@v5" with: - builder: "{% raw %}${{ steps.buildx.outputs.name }}{% endraw %}" + builder: "${{ steps.buildx.outputs.name }}" context: "./" push: false load: true - tags: "{% raw %}${{ env.PLUGIN_NAME }}/nautobot:${{ matrix.nautobot-version }}-py${{ matrix.python-version }}{% endraw %}" + tags: "${{ env.PLUGIN_NAME }}/nautobot:${{ matrix.nautobot-version }}-py${{ matrix.python-version }}" file: "./development/Dockerfile" - cache-from: "type=gha,scope={% raw %}${{ matrix.nautobot-version }}-py${{ matrix.python-version }}{% endraw %}" - cache-to: "type=gha,scope={% raw %}${{ matrix.nautobot-version }}-py${{ matrix.python-version }}{% endraw %}" + cache-from: "type=gha,scope=${{ matrix.nautobot-version }}-py${{ matrix.python-version }}" + cache-to: "type=gha,scope=${{ matrix.nautobot-version }}-py${{ matrix.python-version }}" build-args: | - NAUTOBOT_VER={% raw %}${{ matrix.nautobot-version }}{% endraw %} - PYTHON_VER={% raw %}${{ matrix.python-version }}{% endraw %} + NAUTOBOT_VER=${{ matrix.nautobot-version }} + PYTHON_VER=${{ matrix.python-version }} - name: "Copy credentials" run: "cp development/creds.example.env development/creds.env" - name: "Checking: migrations" @@ -187,8 +187,8 @@ jobs: nautobot-version: "1.6" runs-on: "ubuntu-22.04" env: - INVOKE_NAUTOBOT_DEVICE_LIFECYCLE_MGMT_PYTHON_VER: "{% raw %}${{ matrix.python-version }}{% endraw %}" - INVOKE_NAUTOBOT_DEVICE_LIFECYCLE_MGMT_NAUTOBOT_VER: "{% raw %}${{ matrix.nautobot-version }}{% endraw %}" + INVOKE_NAUTOBOT_DEVICE_LIFECYCLE_MGMT_PYTHON_VER: "${{ matrix.python-version }}" + INVOKE_NAUTOBOT_DEVICE_LIFECYCLE_MGMT_NAUTOBOT_VER: "${{ matrix.nautobot-version }}" steps: - name: "Check out repository code" uses: "actions/checkout@v4" @@ -200,17 +200,17 @@ jobs: - name: "Build" uses: "docker/build-push-action@v5" with: - builder: "{% raw %}${{ steps.buildx.outputs.name }}{% endraw %}" + builder: "${{ steps.buildx.outputs.name }}" context: "./" push: false load: true - tags: "{% raw %}${{ env.PLUGIN_NAME }}/nautobot:${{ matrix.nautobot-version }}-py${{ matrix.python-version }}{% endraw %}" + tags: "${{ env.PLUGIN_NAME }}/nautobot:${{ matrix.nautobot-version }}-py${{ matrix.python-version }}" file: "./development/Dockerfile" - cache-from: "type=gha,scope={% raw %}${{ matrix.nautobot-version }}-py${{ matrix.python-version }}{% endraw %}" - cache-to: "type=gha,scope={% raw %}${{ matrix.nautobot-version }}-py${{ matrix.python-version }}{% endraw %}" + cache-from: "type=gha,scope=${{ matrix.nautobot-version }}-py${{ matrix.python-version }}" + cache-to: "type=gha,scope=${{ matrix.nautobot-version }}-py${{ matrix.python-version }}" build-args: | - NAUTOBOT_VER={% raw %}${{ matrix.nautobot-version }}{% endraw %} - PYTHON_VER={% raw %}${{ matrix.python-version }}{% endraw %} + NAUTOBOT_VER=${{ matrix.nautobot-version }} + PYTHON_VER=${{ matrix.python-version }} - name: "Copy credentials" run: "cp development/creds.example.env development/creds.env" - name: "Use Mysql invoke settings when needed" @@ -242,9 +242,9 @@ jobs: - name: "Upload binaries to release" uses: "svenstaro/upload-release-action@v2" with: - repo_token: "{% raw %}${{ secrets.NTC_GITHUB_TOKEN }}{% endraw %}" # use GH_NAUTOBOT_BOT_TOKEN for Nautobot Org repos. + repo_token: "${{ secrets.NTC_GITHUB_TOKEN }}" # use GH_NAUTOBOT_BOT_TOKEN for Nautobot Org repos. file: "dist/*" - tag: "{% raw %}${{ github.ref }}{% endraw %}" + tag: "${{ github.ref }}" overwrite: true file_glob: true publish_pypi: @@ -272,19 +272,19 @@ jobs: uses: "pypa/gh-action-pypi-publish@release/v1" with: user: "__token__" - password: "{% raw %}${{ secrets.PYPI_API_TOKEN }}{% endraw %}" + password: "${{ secrets.PYPI_API_TOKEN }}" slack-notify: needs: - "publish_gh" - "publish_pypi" runs-on: "ubuntu-22.04" env: - SLACK_WEBHOOK_URL: "{% raw %}${{ secrets.SLACK_WEBHOOK_URL }}{% endraw %}" + SLACK_WEBHOOK_URL: "${{ secrets.SLACK_WEBHOOK_URL }}" SLACK_MESSAGE: >- *NOTIFICATION: NEW-RELEASE-PUBLISHED*\n - Repository: <{% raw %}${{ github.server_url }}/${{ github.repository }}|${{ github.repository }}{% endraw %}>\n - Release: <{% raw %}${{ github.server_url }}/${{ github.repository }}/releases/tag/${{ github.ref_name }}|${{ github.ref_name }}{% endraw %}>\n - Published by: <{% raw %}${{ github.server_url }}/${{ github.actor }}|${{ github.actor }}{% endraw %}> + Repository: <${{ github.server_url }}/${{ github.repository }}|${{ github.repository }}>\n + Release: <${{ github.server_url }}/${{ github.repository }}/releases/tag/${{ github.ref_name }}|${{ github.ref_name }}>\n + Published by: <${{ github.server_url }}/${{ github.actor }}|${{ github.actor }}> steps: - name: "Send a notification to Slack" # ENVs cannot be used directly in job.if. This is a workaround to check @@ -294,17 +294,17 @@ jobs: with: payload: | { - "text": "{% raw %}${{ env.SLACK_MESSAGE }}{% endraw %}", + "text": "${{ env.SLACK_MESSAGE }}", "blocks": [ { "type": "section", "text": { "type": "mrkdwn", - "text": "{% raw %}${{ env.SLACK_MESSAGE }}{% endraw %}" + "text": "${{ env.SLACK_MESSAGE }}" } } ] } env: - SLACK_WEBHOOK_URL: "{% raw %}${{ secrets.SLACK_WEBHOOK_URL }}{% endraw %}" + SLACK_WEBHOOK_URL: "${{ secrets.SLACK_WEBHOOK_URL }}" SLACK_WEBHOOK_TYPE: "INCOMING_WEBHOOK" diff --git a/tasks.py b/tasks.py index 0dece809..d55e9f98 100644 --- a/tasks.py +++ b/tasks.py @@ -78,7 +78,7 @@ def _await_healthy_service(context, service): def _await_healthy_container(context, container_id): while True: result = context.run( - "docker inspect --format='{% raw %}{{.State.Health.Status}}{% endraw %}' " + container_id, + "docker inspect --format='{{.State.Health.Status}}' " + container_id, pty=False, echo=False, hide=True, From 4d3063824220737d4f2885b4f9684056a4fef205 Mon Sep 17 00:00:00 2001 From: Stephen Kiely Date: Thu, 17 Oct 2024 21:38:29 -0500 Subject: [PATCH 10/15] Lower Python version to match the supported minimum version. --- .github/workflows/ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c905215f..3fb6b984 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -94,7 +94,7 @@ jobs: strategy: fail-fast: true matrix: - python-version: ["3.11"] + python-version: ["3.10"] nautobot-version: ["1.4.10"] env: INVOKE_NAUTOBOT_DEVICE_LIFECYCLE_MGMT_PYTHON_VER: "${{ matrix.python-version }}" @@ -179,7 +179,7 @@ jobs: db-backend: ["postgresql"] nautobot-version: ["1.6"] include: - - python-version: "3.11" + - python-version: "3.10" db-backend: "postgresql" nautobot-version: "1.4.10" - python-version: "3.11" From d02bf626802d6115242e718caf07d2da3833f048 Mon Sep 17 00:00:00 2001 From: Stephen Kiely Date: Thu, 17 Oct 2024 21:48:06 -0500 Subject: [PATCH 11/15] Bundled Poetry was too old forcing new install --- development/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/development/Dockerfile b/development/Dockerfile index d355f6ec..318cfb58 100644 --- a/development/Dockerfile +++ b/development/Dockerfile @@ -27,7 +27,7 @@ ENV INVOKE_NAUTOBOT_DEVICE_LIFECYCLE_MGMT_LOCAL=true # and CI and local development may have a newer version of Poetry # Since this is only used for development and we don't ship this container, pinning Poetry back is not expressly necessary # We also don't need virtual environments in container -RUN which poetry || curl -sSL https://install.python-poetry.org | python3 - && \ +RUN curl -sSL https://install.python-poetry.org | python3 - && \ poetry config virtualenvs.create false # !!! USE CAUTION WHEN MODIFYING LINES ABOVE From e7cfc80ac259f2ff7ec1df5526e26e1e102cad09 Mon Sep 17 00:00:00 2001 From: Stephen Kiely Date: Thu, 17 Oct 2024 22:16:29 -0500 Subject: [PATCH 12/15] Update nautobot_config.py Match the LTM Cookie as well. --- development/nautobot_config.py | 310 +++++++-------------------------- 1 file changed, 59 insertions(+), 251 deletions(-) diff --git a/development/nautobot_config.py b/development/nautobot_config.py index 6ec94f2f..ad56d761 100644 --- a/development/nautobot_config.py +++ b/development/nautobot_config.py @@ -1,48 +1,57 @@ -######################### -# # -# Required settings # -# # -######################### +"""Nautobot development configuration file.""" import os import sys -from django.core.exceptions import ImproperlyConfigured from nautobot.core.settings import * # noqa: F403 from nautobot.core.settings_funcs import is_truthy, parse_redis_connection -# Enforce required configuration parameters -for key in [ - "ALLOWED_HOSTS", - "POSTGRES_DB", - "POSTGRES_USER", - "POSTGRES_HOST", - "POSTGRES_PASSWORD", - "REDIS_HOST", - "REDIS_PASSWORD", - "SECRET_KEY", -]: - if not os.environ.get(key): - raise ImproperlyConfigured(f"Required environment variable {key} is missing.") +# +# Debug +# + +DEBUG = is_truthy(os.getenv("NAUTOBOT_DEBUG", False)) +_TESTING = len(sys.argv) > 1 and sys.argv[1] == "test" +if DEBUG and not _TESTING: + DEBUG_TOOLBAR_CONFIG = {"SHOW_TOOLBAR_CALLBACK": lambda _request: True} + + if "debug_toolbar" not in INSTALLED_APPS: # noqa: F405 + INSTALLED_APPS.append("debug_toolbar") # noqa: F405 + if "debug_toolbar.middleware.DebugToolbarMiddleware" not in MIDDLEWARE: # noqa: F405 + MIDDLEWARE.insert(0, "debug_toolbar.middleware.DebugToolbarMiddleware") # noqa: F405 -# This is a list of valid fully-qualified domain names (FQDNs) for the Nautobot server. Nautobot will not permit write -# access to the server via any other hostnames. The first FQDN in the list will be treated as the preferred name. # -# Example: ALLOWED_HOSTS = ['nautobot.example.com', 'nautobot.internal.local'] -ALLOWED_HOSTS = os.getenv("ALLOWED_HOSTS").split(" ") +# Misc. settings +# + +ALLOWED_HOSTS = os.getenv("NAUTOBOT_ALLOWED_HOSTS").split(" ") +SECRET_KEY = os.getenv("NAUTOBOT_SECRET_KEY", "") -# PostgreSQL database configuration. See the Django documentation for a complete list of available parameters: -# https://docs.djangoproject.com/en/stable/ref/settings/#databases +# +# Database +# + +nautobot_db_engine = os.getenv("NAUTOBOT_DB_ENGINE", "django.db.backends.postgresql") +default_db_settings = { + "django.db.backends.postgresql": { + "NAUTOBOT_DB_PORT": "5432", + }, + "django.db.backends.mysql": { + "NAUTOBOT_DB_PORT": "3306", + }, +} DATABASES = { "default": { - "NAME": os.getenv("NAUTOBOT_DB_NAME", "nautobot"), - "USER": os.getenv("NAUTOBOT_DB_USER", ""), - "PASSWORD": os.getenv("NAUTOBOT_DB_PASSWORD", ""), - "HOST": os.getenv("NAUTOBOT_DB_HOST", "localhost"), - "PORT": os.getenv("NAUTOBOT_DB_PORT", ""), - "CONN_MAX_AGE": int(os.getenv("NAUTOBOT_DB_TIMEOUT", 300)), - "ENGINE": os.getenv("NAUTOBOT_DB_ENGINE", "django.db.backends.postgresql"), + "NAME": os.getenv("NAUTOBOT_DB_NAME", "nautobot"), # Database name + "USER": os.getenv("NAUTOBOT_DB_USER", ""), # Database username + "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"] + ), # Database port, default to postgres + "CONN_MAX_AGE": int(os.getenv("NAUTOBOT_DB_TIMEOUT", 300)), # Database timeout + "ENGINE": nautobot_db_engine, } } @@ -50,11 +59,11 @@ if DATABASES["default"]["ENGINE"] == "django.db.backends.mysql": DATABASES["default"]["OPTIONS"] = {"charset": "utf8mb4"} -# The django-redis cache is used to establish concurrent locks using Redis. The -# django-rq settings will use the same instance/database by default. # -# This "default" server is now used by RQ_QUEUES. -# >> See: nautobot.core.settings.RQ_QUEUES +# Redis +# + +# The django-redis cache is used to establish concurrent locks using Redis. CACHES = { "default": { "BACKEND": "django_redis.cache.RedisCache", @@ -66,117 +75,19 @@ } } -# RQ_QUEUES is not set here because it just uses the default that gets imported -# up top via `from nautobot.core.settings import *`. - -# REDIS CACHEOPS -CACHEOPS_REDIS = parse_redis_connection(redis_database=1) - -# This key is used for secure generation of random numbers and strings. It must never be exposed outside of this file. -# For optimal security, SECRET_KEY should be at least 50 characters in length and contain a mix of letters, numbers, and -# symbols. Nautobot will not run without this defined. For more information, see -# https://docs.djangoproject.com/en/stable/ref/settings/#std:setting-SECRET_KEY -SECRET_KEY = os.environ["SECRET_KEY"] - - -######################### -# # -# Optional settings # -# # -######################### - -# Specify one or more name and email address tuples representing Nautobot administrators. These people will be notified of -# application errors (assuming correct email settings are provided). -ADMINS = [ - # ['John Doe', 'jdoe@example.com'], -] - -# URL schemes that are allowed within links in Nautobot -ALLOWED_URL_SCHEMES = ( - "file", - "ftp", - "ftps", - "http", - "https", - "irc", - "mailto", - "sftp", - "ssh", - "tel", - "telnet", - "tftp", - "vnc", - "xmpp", -) - -# Optionally display a persistent banner at the top and/or bottom of every page. HTML is allowed. To display the same -# content in both banners, define BANNER_TOP and set BANNER_BOTTOM = BANNER_TOP. -BANNER_TOP = os.environ.get("BANNER_TOP", "") -BANNER_BOTTOM = os.environ.get("BANNER_BOTTOM", "") - -# Text to include on the login page above the login form. HTML is allowed. -BANNER_LOGIN = os.environ.get("BANNER_LOGIN", "") - -# Maximum number of days to retain logged changes. Set to 0 to retain changes indefinitely. (Default: 90) -CHANGELOG_RETENTION = int(os.environ.get("CHANGELOG_RETENTION", 90)) - -# If True, all origins will be allowed. Other settings restricting allowed origins will be ignored. -# Defaults to False. Setting this to True can be dangerous, as it allows any website to make -# cross-origin requests to yours. Generally you'll want to restrict the list of allowed origins with -# CORS_ALLOWED_ORIGINS or CORS_ALLOWED_ORIGIN_REGEXES. -CORS_ORIGIN_ALLOW_ALL = is_truthy(os.environ.get("CORS_ORIGIN_ALLOW_ALL", False)) - -# A list of origins that are authorized to make cross-site HTTP requests. Defaults to []. -CORS_ALLOWED_ORIGINS = [ - # 'https://hostname.example.com', -] - -# A list of strings representing regexes that match Origins that are authorized to make cross-site -# HTTP requests. Defaults to []. -CORS_ALLOWED_ORIGIN_REGEXES = [ - # r'^(https?://)?(\w+\.)?example\.com$', -] - -# The file path where jobs will be stored. A trailing slash is not needed. Note that the default value of -# this setting is inside the invoking user's home directory. -# JOBS_ROOT = os.path.expanduser('~/.nautobot/jobs') - -# Set to True to enable server debugging. WARNING: Debugging introduces a substantial performance penalty and may reveal -# sensitive information about your installation. Only enable debugging while performing testing. Never enable debugging -# on a production system. -DEBUG = is_truthy(os.environ.get("NAUTOBOT_DEBUG", False)) - -# Enforcement of unique IP space can be toggled on a per-VRF basis. To enforce unique IP space -# within the global table (all prefixes and IP addresses not assigned to a VRF), set -# ENFORCE_GLOBAL_UNIQUE to True. -ENFORCE_GLOBAL_UNIQUE = is_truthy(os.environ.get("ENFORCE_GLOBAL_UNIQUE", False)) - -# Exempt certain models from the enforcement of view permissions. Models listed here will be viewable by all users and -# by anonymous users. List models in the form `.`. Add '*' to this list to exempt all models. -EXEMPT_VIEW_PERMISSIONS = [ - # 'dcim.site', - # 'dcim.region', - # 'ipam.prefix', -] - -# HTTP proxies Nautobot should use when sending outbound HTTP requests (e.g. for webhooks). -# HTTP_PROXIES = { -# 'http': 'http://10.10.1.10:3128', -# 'https': 'http://10.10.1.10:1080', -# } +# +# Celery settings are not defined here because they can be overloaded with +# environment variables. By default they use `CACHES["default"]["LOCATION"]`. +# -# IP addresses recognized as internal to the system. The debugging toolbar will be available only to clients accessing -# Nautobot from an internal IP. -INTERNAL_IPS = ("127.0.0.1", "::1") +# +# Logging +# -# Enable custom logging. Please see the Django documentation for detailed guidance on configuring custom logs: -# https://docs.djangoproject.com/en/stable/topics/logging/ LOG_LEVEL = "DEBUG" if DEBUG else "INFO" -TESTING = len(sys.argv) > 1 and sys.argv[1] == "test" - # Verbose logging during normal development operation, but quiet logging during unit test execution -if not TESTING: +if not _TESTING: LOGGING = { "version": 1, "disable_existing_loggers": False, @@ -211,52 +122,17 @@ }, } -# Setting this to True will display a "maintenance mode" banner at the top of every page. -MAINTENANCE_MODE = False - -# An API consumer can request an arbitrary number of objects =by appending the "limit" parameter to the URL (e.g. -# "?limit=1000"). This setting defines the maximum limit. Setting it to 0 or None will allow an API consumer to request -# all objects by specifying "?limit=0". -MAX_PAGE_SIZE = int(os.environ.get("MAX_PAGE_SIZE", 1000)) - -# The file path where uploaded media such as image attachments are stored. A trailing slash is not needed. Note that -# the default value of this setting is within the invoking user's home directory -# MEDIA_ROOT = os.path.expanduser('~/.nautobot/media') - -# By default uploaded media is stored on the local filesystem. Using Django-storages is also supported. Provide the -# class path of the storage driver in STORAGE_BACKEND and any configuration options in STORAGE_CONFIG. For example: -# STORAGE_BACKEND = 'storages.backends.s3boto3.S3Boto3Storage' -# STORAGE_CONFIG = { -# 'AWS_ACCESS_KEY_ID': 'Key ID', -# 'AWS_SECRET_ACCESS_KEY': 'Secret', -# 'AWS_STORAGE_BUCKET_NAME': 'nautobot', -# 'AWS_S3_REGION_NAME': 'eu-west-1', -# } - -# Expose Prometheus monitoring metrics at the HTTP endpoint '/metrics' -METRICS_ENABLED = True - -# Credentials that Nautobot will uses to authenticate to devices when connecting via NAPALM. -NAPALM_USERNAME = os.environ.get("NAPALM_USERNAME", "") -NAPALM_PASSWORD = os.environ.get("NAPALM_PASSWORD", "") - -# NAPALM timeout (in seconds). (Default: 30) -NAPALM_TIMEOUT = int(os.environ.get("NAPALM_TIMEOUT", 30)) - -# NAPALM optional arguments (see https://napalm.readthedocs.io/en/latest/support/#optional-arguments). Arguments must -# be provided as a dictionary. -NAPALM_ARGS = {} - -# Determine how many objects to display per page within a list. (Default: 50) -PAGINATE_COUNT = int(os.environ.get("PAGINATE_COUNT", 50)) +# +# Apps +# -# Enable installed plugins. Add the name of each plugin to the list. +# Enable installed Apps. Add the name of each App to the list. PLUGINS = [ "nautobot_device_lifecycle_mgmt", ] -# 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. +# Apps configuration settings. These settings are used by various Apps that the user may have installed. +# Each key in the dictionary is the name of an installed App and its value is a dictionary of settings. PLUGINS_CONFIG = { "nautobot_device_lifecycle_mgmt": { "barchart_bar_width": float(os.environ.get("BARCHART_BAR_WIDTH", 0.1)), @@ -265,71 +141,3 @@ "enabled_metrics": [x for x in os.environ.get("NAUTOBOT_DLM_ENABLED_METRICS", "").split(",") if x], }, } - -# When determining the primary IP address for a device, IPv6 is preferred over IPv4 by default. Set this to True to -# prefer IPv4 instead. -PREFER_IPV4 = is_truthy(os.environ.get("PREFER_IPV4", False)) - -# Rack elevation size defaults, in pixels. For best results, the ratio of width to height should be roughly 10:1. -RACK_ELEVATION_DEFAULT_UNIT_HEIGHT = 22 -RACK_ELEVATION_DEFAULT_UNIT_WIDTH = 220 - -# Remote authentication support -REMOTE_AUTH_ENABLED = False -REMOTE_AUTH_BACKEND = "nautobot.core.authentication.RemoteUserBackend" -REMOTE_AUTH_HEADER = "HTTP_REMOTE_USER" -REMOTE_AUTH_AUTO_CREATE_USER = True -REMOTE_AUTH_DEFAULT_GROUPS = [] -REMOTE_AUTH_DEFAULT_PERMISSIONS = {} - -# This determines how often the GitHub API is called to check the latest release of Nautobot. Must be at least 1 hour. -RELEASE_CHECK_TIMEOUT = 24 * 3600 - -# This repository is used to check whether there is a new release of Nautobot available. Set to None to disable the -# version check or use the URL below to check for release in the official Nautobot repository. -RELEASE_CHECK_URL = None -# RELEASE_CHECK_URL = 'https://api.github.com/repos/nautobot/nautobot/releases' - -# Maximum execution time for background tasks, in seconds. -RQ_DEFAULT_TIMEOUT = 300 - -# The length of time (in seconds) for which a user will remain logged into the web UI before being prompted to -# re-authenticate. (Default: 1209600 [14 days]) -SESSION_COOKIE_AGE = 1209600 # 2 weeks, in seconds - - -# By default, Nautobot will store session data in the database. Alternatively, a file path can be specified here to use -# local file storage instead. (This can be useful for enabling authentication on a standby instance with read-only -# database access.) Note that the user as which Nautobot runs must have read and write permissions to this path. -SESSION_FILE_PATH = None - -# Always use IPython for shell_plus -SHELL_PLUS = "ipython" - -# Configure SSO, for more information see docs/configuration/authentication/sso.md -SOCIAL_AUTH_ENABLED = False - -# Time zone (default: UTC) -TIME_ZONE = os.environ.get("TIME_ZONE", "UTC") - -# Date/time formatting. See the following link for supported formats: -# https://docs.djangoproject.com/en/stable/ref/templates/builtins/#date -DATE_FORMAT = os.environ.get("DATE_FORMAT", "N j, Y") -SHORT_DATE_FORMAT = os.environ.get("SHORT_DATE_FORMAT", "Y-m-d") -TIME_FORMAT = os.environ.get("TIME_FORMAT", "g:i a") -SHORT_TIME_FORMAT = os.environ.get("SHORT_TIME_FORMAT", "H:i:s") -DATETIME_FORMAT = os.environ.get("DATETIME_FORMAT", "N j, Y g:i a") -SHORT_DATETIME_FORMAT = os.environ.get("SHORT_DATETIME_FORMAT", "Y-m-d H:i") - -# A list of strings designating all applications that are enabled in this Django installation. Each string should be -# a dotted Python path to an application configuration class (preferred), or a package containing an application. -# https://nautobot.readthedocs.io/en/latest/configuration/optional-settings/#extra-applications -EXTRA_INSTALLED_APPS = os.environ["EXTRA_INSTALLED_APPS"].split(",") if os.environ.get("EXTRA_INSTALLED_APPS") else [] - -# Django Debug Toolbar -if DEBUG: - DEBUG_TOOLBAR_CONFIG = {"SHOW_TOOLBAR_CALLBACK": lambda _request: DEBUG and not TESTING} - if "debug_toolbar" not in INSTALLED_APPS: # noqa: F405 - INSTALLED_APPS.append("debug_toolbar") # noqa: F405 - if "debug_toolbar.middleware.DebugToolbarMiddleware" not in MIDDLEWARE: # noqa: F405 - MIDDLEWARE.insert(0, "debug_toolbar.middleware.DebugToolbarMiddleware") # noqa: F405 From e5c8ae73977172ea28ce1c4fa8b228fef5c12250 Mon Sep 17 00:00:00 2001 From: Stephen Kiely Date: Thu, 17 Oct 2024 22:29:04 -0500 Subject: [PATCH 13/15] Create invoke.mysql.yml --- invoke.mysql.yml | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 invoke.mysql.yml diff --git a/invoke.mysql.yml b/invoke.mysql.yml new file mode 100644 index 00000000..944c5233 --- /dev/null +++ b/invoke.mysql.yml @@ -0,0 +1,12 @@ +--- +nautobot_plugin_device_lifecycle_mgmt: + project_name: "nautobot-plugin-device-lifecycle-mgmt" + nautobot_ver: "1.6.0" + local: false + python_ver: "3.11" + compose_dir: "development" + compose_files: + - "docker-compose.base.yml" + - "docker-compose.redis.yml" + - "docker-compose.mysql.yml" + - "docker-compose.dev.yml" From 02996dceb50ecb6ac724ef21b693590dedb758cb Mon Sep 17 00:00:00 2001 From: Stephen Kiely Date: Fri, 18 Oct 2024 08:47:17 -0500 Subject: [PATCH 14/15] Update ci.yml --- .github/workflows/ci.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3fb6b984..e50dd138 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -8,6 +8,7 @@ on: # yamllint disable-line rule:truthy rule:comments branches: - "main" - "develop" + - "ltm-1.6" tags: - "v*" pull_request: ~ From e13f9f0aee8576a48f3cb2e28730b4f133985001 Mon Sep 17 00:00:00 2001 From: Stephen Kiely Date: Fri, 18 Oct 2024 12:50:10 -0500 Subject: [PATCH 15/15] Update .github/workflows/ci.yml Co-authored-by: Gary Snider <75227981+gsnider2195@users.noreply.github.com> --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e50dd138..4af25e11 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -243,7 +243,7 @@ jobs: - name: "Upload binaries to release" uses: "svenstaro/upload-release-action@v2" with: - repo_token: "${{ secrets.NTC_GITHUB_TOKEN }}" # use GH_NAUTOBOT_BOT_TOKEN for Nautobot Org repos. + repo_token: "${{ secrets.GH_NAUTOBOT_BOT_TOKEN }}" # use GH_NAUTOBOT_BOT_TOKEN for Nautobot Org repos. file: "dist/*" tag: "${{ github.ref }}" overwrite: true