diff --git a/.cookiecutter.json b/.cookiecutter.json
index e37d5a731..ca90fd5bc 100644
--- a/.cookiecutter.json
+++ b/.cookiecutter.json
@@ -4,15 +4,15 @@
"full_name": "Network to Code, LLC",
"email": "opensource@networktocode.com",
"github_org": "nautobot",
- "plugin_name": "nautobot_ssot",
+ "app_name": "nautobot_ssot",
"verbose_name": "Single Source of Truth",
- "plugin_slug": "nautobot-ssot",
- "project_slug": "nautobot-plugin-ssot",
- "repo_url": "https://github.com/nautobot/nautobot-plugin-ssot/",
+ "app_slug": "nautobot-ssot",
+ "project_slug": "nautobot-app-ssot",
+ "repo_url": "https://github.com/nautobot/nautobot-app-ssot/",
"base_url": "ssot",
"min_nautobot_version": "2.0.0",
"max_nautobot_version": "2.9999",
- "camel_name": "NautobotSSOTPlugin",
+ "camel_name": "NautobotSSOTApp",
"project_short_description": "Nautobot Single Source of Truth",
"model_class_name": "None",
"open_source_license": "Apache-2.0",
@@ -33,3 +33,4 @@
}
}
}
+
diff --git a/.github/PULL_REQUEST_TEMPLATE/pull_request_template.md b/.github/PULL_REQUEST_TEMPLATE/pull_request_template.md
index 3950227cd..b1eba0706 100644
--- a/.github/PULL_REQUEST_TEMPLATE/pull_request_template.md
+++ b/.github/PULL_REQUEST_TEMPLATE/pull_request_template.md
@@ -31,5 +31,5 @@
- [ ] Attached Screenshots, Payload Example
- [ ] Unit, Integration Tests
- [ ] Documentation Updates (when adding/changing features)
-- [ ] Example Plugin Updates (when adding/changing features)
+- [ ] Example App Updates (when adding/changing features)
- [ ] Outline Remaining Work, Constraints from Design
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 12857f050..55676ac04 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -13,7 +13,7 @@ on: # yamllint disable-line rule:truthy rule:comments
pull_request: ~
env:
- PLUGIN_NAME: "nautobot-plugin-ssot"
+ PLUGIN_NAME: "nautobot-app-ssot"
jobs:
black:
diff --git a/.github/workflows/upstream_testing.yml b/.github/workflows/upstream_testing.yml
index b69810f4a..5351934f6 100644
--- a/.github/workflows/upstream_testing.yml
+++ b/.github/workflows/upstream_testing.yml
@@ -10,4 +10,4 @@ jobs:
uses: "nautobot/nautobot/.github/workflows/plugin_upstream_testing_base.yml@develop"
with: # Below could potentially be collapsed into a single argument if a concrete relationship between both is enforced
invoke_context_name: "NAUTOBOT_SSOT"
- plugin_name: "nautobot-plugin-ssot"
+ plugin_name: "nautobot-app-ssot"
diff --git a/README.md b/README.md
index 1bf3ef6e3..47559ab6e 100644
--- a/README.md
+++ b/README.md
@@ -1,10 +1,10 @@
# Nautobot Single Source of Truth (SSoT)
-
+
-
-
+
+
@@ -40,17 +40,17 @@ Read more about integrations [here](https://docs.nautobot.com/projects/ssot/en/l
---
The dashboard view of the app.
-![Dashboard View](https://raw.githubusercontent.com/nautobot/nautobot-plugin-ssot/develop/docs/images/dashboard_initial.png)
+![Dashboard View](https://raw.githubusercontent.com/nautobot/nautobot-app-ssot/develop/docs/images/dashboard_initial.png)
---
The detailed view of the example data source that is prepackaged within this app.
-![Data Source Detail View](https://raw.githubusercontent.com/nautobot/nautobot-plugin-ssot/develop/docs/images/data_source_detail.png)
+![Data Source Detail View](https://raw.githubusercontent.com/nautobot/nautobot-app-ssot/develop/docs/images/data_source_detail.png)
---
The detailed view of an executed sync.
-![Sync Detail View](https://raw.githubusercontent.com/nautobot/nautobot-plugin-ssot/develop/docs/images/sync_detail.png)
+![Sync Detail View](https://raw.githubusercontent.com/nautobot/nautobot-app-ssot/develop/docs/images/sync_detail.png)
---
@@ -85,7 +85,7 @@ The SSoT framework includes a number of integrations with external Systems of Re
### Contributing to the Documentation
-You can find all the Markdown source for the app documentation under the [`docs`](https://github.com/nautobot/nautobot-plugin-ssot/tree/develop/docs) folder in this repository. For simple edits, a Markdown capable editor is sufficient: clone the repository and edit away.
+You can find all the Markdown source for the app documentation under the [`docs`](https://github.com/nautobot/nautobot-app-ssot/tree/develop/docs) folder in this repository. For simple edits, a Markdown capable editor is sufficient: clone the repository and edit away.
If you need to view the fully-generated documentation site, you can build it with [MkDocs](https://www.mkdocs.org/). A container hosting the documentation can be started using the `invoke` commands (details in the [Development Environment Guide](https://docs.nautobot.com/projects/ssot/en/latest/dev/dev_environment/#docker-development-environment)) on [http://localhost:8001](http://localhost:8001). Using this container, as your changes to the documentation are saved, they will be automatically rebuilt and any pages currently being viewed will be reloaded in your browser.
diff --git a/development/nautobot_config.py b/development/nautobot_config.py
index 25f9d9892..07f784400 100644
--- a/development/nautobot_config.py
+++ b/development/nautobot_config.py
@@ -230,6 +230,7 @@
"infoblox_username": os.getenv("NAUTOBOT_SSOT_INFOBLOX_USERNAME"),
"infoblox_verify_ssl": is_truthy(os.getenv("NAUTOBOT_SSOT_INFOBLOX_VERIFY_SSL", True)),
"infoblox_wapi_version": os.getenv("NAUTOBOT_SSOT_INFOBLOX_WAPI_VERSION", "v2.12"),
+ "infoblox_network_view": os.getenv("NAUTOBOT_SSOT_INFOBLOX_NETWORK_VIEW", ""),
"ipfabric_api_token": os.getenv("NAUTOBOT_SSOT_IPFABRIC_API_TOKEN"),
"ipfabric_host": os.getenv("NAUTOBOT_SSOT_IPFABRIC_HOST"),
"ipfabric_ssl_verify": is_truthy(os.getenv("NAUTOBOT_SSOT_IPFABRIC_SSL_VERIFY", "False")),
diff --git a/docs/admin/compatibility_matrix.md b/docs/admin/compatibility_matrix.md
index f6d9e6b78..b0298bc01 100644
--- a/docs/admin/compatibility_matrix.md
+++ b/docs/admin/compatibility_matrix.md
@@ -17,3 +17,4 @@ While that last supported version will not be strictly enforced--via the max_ver
| 2.0.0 | 2.0.0 | 2.99.09 |
| 2.0.1 | 2.0.0 | 2.99.09 |
| 2.0.2 | 2.0.0 | 2.99.09 |
+| 2.1.0 | 2.0.0 | 2.99.09 |
diff --git a/docs/admin/integrations/infoblox_setup.md b/docs/admin/integrations/infoblox_setup.md
index 736ffea13..e23abc61f 100644
--- a/docs/admin/integrations/infoblox_setup.md
+++ b/docs/admin/integrations/infoblox_setup.md
@@ -29,6 +29,7 @@ Integration behavior can be controlled with the following settings:
| infoblox_import_objects_vlan_views | False | Import VLAN views from Infoblox to Nautobot. |
| infoblox_import_objects_vlans | False | Import VLANs from Infoblox to Nautobot. |
| infoblox_import_subnets | N/A | List of Subnets in CIDR string notation to filter import to. |
+| infoblox_network_view | N/A | Only load IPAddresses from a specific Infoblox Network View. |
Below is an example snippet from `nautobot_config.py` that demonstrates how to enable and configure Infoblox integration:
diff --git a/docs/admin/integrations/ipfabric_setup.md b/docs/admin/integrations/ipfabric_setup.md
index 33a161776..148231ed5 100644
--- a/docs/admin/integrations/ipfabric_setup.md
+++ b/docs/admin/integrations/ipfabric_setup.md
@@ -57,6 +57,7 @@ PLUGINS_CONFIG = {
| `ipfabric_safe_delete_location_status` | The status that is set for a Location when the `Safe Delete Mode` flag is set in the Job. | `Decommissioning` |
| `ipfabric_safe_delete_vlan_status` | The status that is set for a VLAN when the `Safe Delete Mode` flag is set in the Job. | `Deprecated` |
| `ipfabric_safe_delete_ipaddress_status` | The status that is set for an IP Address when the `Safe Delete Mode` flag is set in the Job. | `Deprecated` |
+| `ipfabric_use_canonical_interface_name` | Whether to attempt to elongate interface names as found in IP Fabric. | `False` |
Below is an example snippet from `nautobot_config.py` that demonstrates how to enable and configure the IPFabric SSoT integration along with the optional settings:
@@ -80,6 +81,7 @@ PLUGINS_CONFIG = {
"ipfabric_safe_delete_location_status": os.environ.get("NAUTOBOT_SSOT_IPFABRIC_LOCATION_DELETE_STATUS"),
"ipfabric_safe_delete_vlan_status": os.environ.get("NAUTOBOT_SSOT_IPFABRIC_VLAN_DELETE_STATUS"),
"ipfabric_safe_delete_ipaddress_status": os.environ.get("NAUTOBOT_SSOT_IPFABRIC_IPADDRESS_DELETE_STATUS"),
+ "ipfabric_use_canonical_interface_name": True,
}
}
```
diff --git a/docs/admin/release_notes/version_1.0.md b/docs/admin/release_notes/version_1.0.md
index 8eeaa8803..bf80cef44 100644
--- a/docs/admin/release_notes/version_1.0.md
+++ b/docs/admin/release_notes/version_1.0.md
@@ -4,15 +4,15 @@
### Changed
-- [#8](https://github.com/nautobot/nautobot-plugin-ssot/pull/8) - Switched from Travis CI to GitHub Actions.
+- [#8](https://github.com/nautobot/nautobot-app-ssot/pull/8) - Switched from Travis CI to GitHub Actions.
### Fixed
-- [#9](https://github.com/nautobot/nautobot-plugin-ssot/pull/9) - Added missing `name` string to `jobs/examples.py`.
+- [#9](https://github.com/nautobot/nautobot-app-ssot/pull/9) - Added missing `name` string to `jobs/examples.py`.
### Removed
-- [#7](https://github.com/nautobot/nautobot-plugin-ssot/pull/7) - Removed unnecessary `markdown-include` development/documentation dependency.
+- [#7](https://github.com/nautobot/nautobot-app-ssot/pull/7) - Removed unnecessary `markdown-include` development/documentation dependency.
## [v1.0.0] - 2021-08-03
diff --git a/docs/admin/release_notes/version_1.1.md b/docs/admin/release_notes/version_1.1.md
index 5e86cfb76..e75abde74 100644
--- a/docs/admin/release_notes/version_1.1.md
+++ b/docs/admin/release_notes/version_1.1.md
@@ -4,29 +4,29 @@
### Fixed
-- [#48](https://github.com/nautobot/nautobot-plugin-ssot/pull/48) - Fix introduced bug in #43, using a nonexistent method in an object.
+- [#48](https://github.com/nautobot/nautobot-app-ssot/pull/48) - Fix introduced bug in #43, using a nonexistent method in an object.
## v1.1.1 - 2022-05-06
### Added
-- [#43](https://github.com/nautobot/nautobot-plugin-ssot/pull/43) - The detailed SSoT log output contains a summary info message about the diff changes.
+- [#43](https://github.com/nautobot/nautobot-app-ssot/pull/43) - The detailed SSoT log output contains a summary info message about the diff changes.
### Fixed
-- [#42](https://github.com/nautobot/nautobot-plugin-ssot/pull/42) - Use format_html to create all HTML pieces in render_diff and combine with mark_safe when is formatted twice.
-- [#40](https://github.com/nautobot/nautobot-plugin-ssot/pull/40) - Handle gracefully unexpected input type for humanize_bytes filter.
+- [#42](https://github.com/nautobot/nautobot-app-ssot/pull/42) - Use format_html to create all HTML pieces in render_diff and combine with mark_safe when is formatted twice.
+- [#40](https://github.com/nautobot/nautobot-app-ssot/pull/40) - Handle gracefully unexpected input type for humanize_bytes filter.
## v1.1.0 - 2022-02-03
### Added
-- [#11](https://github.com/nautobot/nautobot-plugin-ssot/issues/11) - Add option to profile memory usage during job execution.
-- [#14](https://github.com/nautobot/nautobot-plugin-ssot/pull/14), [#22](https://github.com/nautobot/nautobot-plugin-ssot/pull/22) - Added Prefix sync to example jobs, added Celery worker to dev environment.
-- [#15](https://github.com/nautobot/nautobot-plugin-ssot/pull/15) - Added `load_source_adapter`, `load_target_adapter`, `calculate_diff`, and `execute_sync` API methods that Job implementations can override instead of overriding the generalized `sync_data` method.
-- [#21](https://github.com/nautobot/nautobot-plugin-ssot/pull/21) - Add performance stats in Sync job detail view.
+- [#11](https://github.com/nautobot/nautobot-app-ssot/issues/11) - Add option to profile memory usage during job execution.
+- [#14](https://github.com/nautobot/nautobot-app-ssot/pull/14), [#22](https://github.com/nautobot/nautobot-app-ssot/pull/22) - Added Prefix sync to example jobs, added Celery worker to dev environment.
+- [#15](https://github.com/nautobot/nautobot-app-ssot/pull/15) - Added `load_source_adapter`, `load_target_adapter`, `calculate_diff`, and `execute_sync` API methods that Job implementations can override instead of overriding the generalized `sync_data` method.
+- [#21](https://github.com/nautobot/nautobot-app-ssot/pull/21) - Add performance stats in Sync job detail view.
### Fixed
-- [#13](https://github.com/nautobot/nautobot-plugin-ssot/issues/13) - Handle pagination of Nautobot REST API in example jobs.
-- [#18](https://github.com/nautobot/nautobot-plugin-ssot/pull/18) - Don't skip diff and sync if either of the source or target adapters initially contains no data.
\ No newline at end of file
+- [#13](https://github.com/nautobot/nautobot-app-ssot/issues/13) - Handle pagination of Nautobot REST API in example jobs.
+- [#18](https://github.com/nautobot/nautobot-app-ssot/pull/18) - Don't skip diff and sync if either of the source or target adapters initially contains no data.
\ No newline at end of file
diff --git a/docs/admin/release_notes/version_1.2.md b/docs/admin/release_notes/version_1.2.md
index 2a2eb63b9..9259e879e 100644
--- a/docs/admin/release_notes/version_1.2.md
+++ b/docs/admin/release_notes/version_1.2.md
@@ -6,8 +6,8 @@
### Fixed
-- [#58](https://github.com/nautobot/nautobot-plugin-ssot/pull/58) - Change `message` and `object_repr` from 200 `CharField` field, to a `TextField`
+- [#58](https://github.com/nautobot/nautobot-app-ssot/pull/58) - Change `message` and `object_repr` from 200 `CharField` field, to a `TextField`
### Changed
-- [#56](https://github.com/nautobot/nautobot-plugin-ssot/pull/56) - Drop Python 3.6 support
+- [#56](https://github.com/nautobot/nautobot-app-ssot/pull/56) - Drop Python 3.6 support
diff --git a/docs/admin/release_notes/version_1.3.md b/docs/admin/release_notes/version_1.3.md
index 3a00247b4..75febde37 100644
--- a/docs/admin/release_notes/version_1.3.md
+++ b/docs/admin/release_notes/version_1.3.md
@@ -5,42 +5,42 @@
### Added
-- [#95](https://github.com/nautobot/nautobot-plugin-ssot/pull/95) - Add Metrics
-- [#70](https://github.com/nautobot/nautobot-plugin-ssot/pull/70) - Enable Upstream Testing
-- [#87](https://github.com/nautobot/nautobot-plugin-ssot/pull/87) - Rename upstream.yml to upstream_testing.yml
+- [#95](https://github.com/nautobot/nautobot-app-ssot/pull/95) - Add Metrics
+- [#70](https://github.com/nautobot/nautobot-app-ssot/pull/70) - Enable Upstream Testing
+- [#87](https://github.com/nautobot/nautobot-app-ssot/pull/87) - Rename upstream.yml to upstream_testing.yml
### Fixed
-- [#93](https://github.com/nautobot/nautobot-plugin-ssot/pull/93) - fix-92: Defer loading JSONField 'diff' in queryset
-- [#101](https://github.com/nautobot/nautobot-plugin-ssot/pull/101) - Fix packaging version in pyproject.toml.
+- [#93](https://github.com/nautobot/nautobot-app-ssot/pull/93) - fix-92: Defer loading JSONField 'diff' in queryset
+- [#101](https://github.com/nautobot/nautobot-app-ssot/pull/101) - Fix packaging version in pyproject.toml.
### Changed
-- [#62](https://github.com/nautobot/nautobot-plugin-ssot/pull/62) - Add slack notify CI step for release
-- [#73](https://github.com/nautobot/nautobot-plugin-ssot/pull/73) - Update environment to ntc cookie-cutter
-- [#89](https://github.com/nautobot/nautobot-plugin-ssot/pull/89) - Use generic templates
+- [#62](https://github.com/nautobot/nautobot-app-ssot/pull/62) - Add slack notify CI step for release
+- [#73](https://github.com/nautobot/nautobot-app-ssot/pull/73) - Update environment to ntc cookie-cutter
+- [#89](https://github.com/nautobot/nautobot-app-ssot/pull/89) - Use generic templates
### Documentation
-- [#80](https://github.com/nautobot/nautobot-plugin-ssot/pull/80) - Documentation Update
-- [#72](https://github.com/nautobot/nautobot-plugin-ssot/pull/72) - Update __init__.py to better describe the app in the UI
-- [#94](https://github.com/nautobot/nautobot-plugin-ssot/pull/94) - Add Justin and Leo as codeowners
+- [#80](https://github.com/nautobot/nautobot-app-ssot/pull/80) - Documentation Update
+- [#72](https://github.com/nautobot/nautobot-app-ssot/pull/72) - Update __init__.py to better describe the app in the UI
+- [#94](https://github.com/nautobot/nautobot-app-ssot/pull/94) - Add Justin and Leo as codeowners
## v1.3.1 - 2023-05-02
### Fixes
-- [#108](https://github.com/nautobot/nautobot-plugin-ssot/pull/108) - Fix Prometheus Dependency
+- [#108](https://github.com/nautobot/nautobot-app-ssot/pull/108) - Fix Prometheus Dependency
-
## v1.3.2 - 2023-05-23
### Fixed
-- [#119](https://github.com/nautobot/nautobot-plugin-ssot/pull/119) Fix search bar in SSoT Sync Logs
-- [#112](https://github.com/nautobot/nautobot-plugin-ssot/pull/112) Fix error in documentation
+- [#119](https://github.com/nautobot/nautobot-app-ssot/pull/119) Fix search bar in SSoT Sync Logs
+- [#112](https://github.com/nautobot/nautobot-app-ssot/pull/112) Fix error in documentation
### Changed
-- [#120](https://github.com/nautobot/nautobot-plugin-ssot/pull/120) Bump requests from 2.29.0 to 2.31.0
-- [#117](https://github.com/nautobot/nautobot-plugin-ssot/pull/117) Bump pymdown-extensions from 9.11 to 10.0
-- [#116](https://github.com/nautobot/nautobot-plugin-ssot/pull/116) Bump django from 3.2.18 to 3.2.19
+- [#120](https://github.com/nautobot/nautobot-app-ssot/pull/120) Bump requests from 2.29.0 to 2.31.0
+- [#117](https://github.com/nautobot/nautobot-app-ssot/pull/117) Bump pymdown-extensions from 9.11 to 10.0
+- [#116](https://github.com/nautobot/nautobot-app-ssot/pull/116) Bump django from 3.2.18 to 3.2.19
diff --git a/docs/admin/release_notes/version_1.4.md b/docs/admin/release_notes/version_1.4.md
index 25d784cb7..b1dc5253f 100644
--- a/docs/admin/release_notes/version_1.4.md
+++ b/docs/admin/release_notes/version_1.4.md
@@ -4,20 +4,20 @@
## Added
-- [136](https://github.com/nautobot/nautobot-plugin-ssot/pull/136) - Add Cisco ACI integration by @snaselj
-- [138](https://github.com/nautobot/nautobot-plugin-ssot/pull/138) - Add Arista CloudVision integration by @snaselj
-- [139](https://github.com/nautobot/nautobot-plugin-ssot/pull/139) - Add ServiceNow integration by @snaselj
-- [137](https://github.com/nautobot/nautobot-plugin-ssot/pull/137) - Add IPFabric integration by @snaselj
+- [136](https://github.com/nautobot/nautobot-app-ssot/pull/136) - Add Cisco ACI integration by @snaselj
+- [138](https://github.com/nautobot/nautobot-app-ssot/pull/138) - Add Arista CloudVision integration by @snaselj
+- [139](https://github.com/nautobot/nautobot-app-ssot/pull/139) - Add ServiceNow integration by @snaselj
+- [137](https://github.com/nautobot/nautobot-app-ssot/pull/137) - Add IPFabric integration by @snaselj
## Changed
-- [88](https://github.com/nautobot/nautobot-plugin-ssot/pull/88) - fix: Do not fail example data source job with invalid data by @snaselj
-- [135](https://github.com/nautobot/nautobot-plugin-ssot/pull/135) - Sync tasks with template by @snaselj
-- [134](https://github.com/nautobot/nautobot-plugin-ssot/pull/134) - Prepare for integrations and add Infoblox integration by @snaselj
-- [124](https://github.com/nautobot/nautobot-plugin-ssot/pull/124) - 1.3.2 back to develop by @Kircheneer
-- [145](https://github.com/nautobot/nautobot-plugin-ssot/pull/145) - Wrap saving diff in try/except for large diff errors by @jmcgill298
-- [146](https://github.com/nautobot/nautobot-plugin-ssot/pull/146) - Use DjangoJSONEncoder for diff field by @jmcgill298
-- [155](https://github.com/nautobot/nautobot-plugin-ssot/pull/155) - Port IP Fabric release v2.0.2 into integration PR by @pke11y
-- [153](https://github.com/nautobot/nautobot-plugin-ssot/pull/153) - Implementing base models/adapters for Nautobot side functionality. by @Kircheneer
-- [199](https://github.com/nautobot/nautobot-plugin-ssot/pull/199) - style: :bug: Support generic folder path (ported fomr a commit by @jasonjuenger) by @chadell
-- [152](https://github.com/nautobot/nautobot-plugin-ssot/pull/152) - Finalize Integrations by @snaselj
+- [88](https://github.com/nautobot/nautobot-app-ssot/pull/88) - fix: Do not fail example data source job with invalid data by @snaselj
+- [135](https://github.com/nautobot/nautobot-app-ssot/pull/135) - Sync tasks with template by @snaselj
+- [134](https://github.com/nautobot/nautobot-app-ssot/pull/134) - Prepare for integrations and add Infoblox integration by @snaselj
+- [124](https://github.com/nautobot/nautobot-app-ssot/pull/124) - 1.3.2 back to develop by @Kircheneer
+- [145](https://github.com/nautobot/nautobot-app-ssot/pull/145) - Wrap saving diff in try/except for large diff errors by @jmcgill298
+- [146](https://github.com/nautobot/nautobot-app-ssot/pull/146) - Use DjangoJSONEncoder for diff field by @jmcgill298
+- [155](https://github.com/nautobot/nautobot-app-ssot/pull/155) - Port IP Fabric release v2.0.2 into integration PR by @pke11y
+- [153](https://github.com/nautobot/nautobot-app-ssot/pull/153) - Implementing base models/adapters for Nautobot side functionality. by @Kircheneer
+- [199](https://github.com/nautobot/nautobot-app-ssot/pull/199) - style: :bug: Support generic folder path (ported fomr a commit by @jasonjuenger) by @chadell
+- [152](https://github.com/nautobot/nautobot-app-ssot/pull/152) - Finalize Integrations by @snaselj
diff --git a/docs/admin/release_notes/version_1.5.md b/docs/admin/release_notes/version_1.5.md
index ddf128252..68f81cf46 100644
--- a/docs/admin/release_notes/version_1.5.md
+++ b/docs/admin/release_notes/version_1.5.md
@@ -4,5 +4,5 @@
## Changed
-- [206](https://github.com/nautobot/nautobot-plugin-ssot/pull/206) - Update docs pins for py3.7 compatibility by @cmsirbu
-- [207][https://github.com/nautobot/nautobot-plugin-ssot/pull/207] Drop Python 3.7 Support by @jdrew82
+- [206](https://github.com/nautobot/nautobot-app-ssot/pull/206) - Update docs pins for py3.7 compatibility by @cmsirbu
+- [207][https://github.com/nautobot/nautobot-app-ssot/pull/207] Drop Python 3.7 Support by @jdrew82
diff --git a/docs/admin/release_notes/version_1.6.md b/docs/admin/release_notes/version_1.6.md
index 40c23d0ce..5528dc0f1 100644
--- a/docs/admin/release_notes/version_1.6.md
+++ b/docs/admin/release_notes/version_1.6.md
@@ -4,16 +4,16 @@
## Added
-- [221](https://github.com/nautobot/nautobot-plugin-ssot/pull/221) - Add Device42 integration by @jdrew82
-- [222](https://github.com/nautobot/nautobot-plugin-ssot/pull/222) - Add conflicting message link by @snaselj
-- [224](https://github.com/nautobot/nautobot-plugin-ssot/pull/224) - Allow Skipping Conflicting Apps Check by @snaselj
+- [221](https://github.com/nautobot/nautobot-app-ssot/pull/221) - Add Device42 integration by @jdrew82
+- [222](https://github.com/nautobot/nautobot-app-ssot/pull/222) - Add conflicting message link by @snaselj
+- [224](https://github.com/nautobot/nautobot-app-ssot/pull/224) - Allow Skipping Conflicting Apps Check by @snaselj
## Changed
-- [219](https://github.com/nautobot/nautobot-plugin-ssot/pull/219) - Attempt fixing CI error in #214 by @Kircheneer
-- [220](https://github.com/nautobot/nautobot-plugin-ssot/pull/220) - Update ci.yml by @Kircheneer
-- [214](https://github.com/nautobot/nautobot-plugin-ssot/pull/214) - Sync Main to Develop for 1.5.0 by @jdrew82
-- [218](https://github.com/nautobot/nautobot-plugin-ssot/pull/218) - Fixes contrib.NautobotModel not returning objects on update/delete by @Kircheneer
-- [161](https://github.com/nautobot/nautobot-plugin-ssot/pull/161) - Reverts ChatOps dependency removal by @snaselj
-- [213](https://github.com/nautobot/nautobot-plugin-ssot/pull/213) - fix: :bug: Several fixes in the ACI integration by @chadell
-- [205](https://github.com/nautobot/nautobot-plugin-ssot/pull/205) - Migrate PR #164 from Arista Child Repo by @qduk
+- [219](https://github.com/nautobot/nautobot-app-ssot/pull/219) - Attempt fixing CI error in #214 by @Kircheneer
+- [220](https://github.com/nautobot/nautobot-app-ssot/pull/220) - Update ci.yml by @Kircheneer
+- [214](https://github.com/nautobot/nautobot-app-ssot/pull/214) - Sync Main to Develop for 1.5.0 by @jdrew82
+- [218](https://github.com/nautobot/nautobot-app-ssot/pull/218) - Fixes contrib.NautobotModel not returning objects on update/delete by @Kircheneer
+- [161](https://github.com/nautobot/nautobot-app-ssot/pull/161) - Reverts ChatOps dependency removal by @snaselj
+- [213](https://github.com/nautobot/nautobot-app-ssot/pull/213) - fix: :bug: Several fixes in the ACI integration by @chadell
+- [205](https://github.com/nautobot/nautobot-app-ssot/pull/205) - Migrate PR #164 from Arista Child Repo by @qduk
diff --git a/docs/admin/release_notes/version_2.0.md b/docs/admin/release_notes/version_2.0.md
index 1c74b3104..d8217eb4c 100644
--- a/docs/admin/release_notes/version_2.0.md
+++ b/docs/admin/release_notes/version_2.0.md
@@ -41,44 +41,44 @@
### Changed
-- [168](https://github.com/nautobot/nautobot-plugin-ssot/pull/168) - Update for Nautobot 2.0.0.rc1 Support by @jdrew82 in #168
+- [168](https://github.com/nautobot/nautobot-app-ssot/pull/168) - Update for Nautobot 2.0.0.rc1 Support by @jdrew82 in #168
- Fixed use of slug on Platform in Arista CVP integration.
## v2.0.0-rc.2 - 2023-09-8
### Changed
-- [200](https://github.com/nautobot/nautobot-plugin-ssot/pull/200) - Enable RC2 Support by @jdrew82
+- [200](https://github.com/nautobot/nautobot-app-ssot/pull/200) - Enable RC2 Support by @jdrew82
## v2.0.0 - 2023-10-5
### Changed
-- [228](https://github.com/nautobot/nautobot-plugin-ssot/pull/228) - Merge Main Back to Dev for 1.6.0 by @jdrew82
-- [231](https://github.com/nautobot/nautobot-plugin-ssot/pull/231) - Update Device42 Integration for Nautobot 2.0 by @jdrew82
-- [230](https://github.com/nautobot/nautobot-plugin-ssot/pull/230) - Update to Nautobot 2.0 by @jdrew82
+- [228](https://github.com/nautobot/nautobot-app-ssot/pull/228) - Merge Main Back to Dev for 1.6.0 by @jdrew82
+- [231](https://github.com/nautobot/nautobot-app-ssot/pull/231) - Update Device42 Integration for Nautobot 2.0 by @jdrew82
+- [230](https://github.com/nautobot/nautobot-app-ssot/pull/230) - Update to Nautobot 2.0 by @jdrew82
## v2.0.1 - 2023-11-6
## Changed
-- [242](https://github.com/nautobot/nautobot-plugin-ssot/pull/242) - Fix Infoblox import_subnets setting by @jdrew82
-- [252](https://github.com/nautobot/nautobot-plugin-ssot/pull/252) - Add CODEOWNERS for the various integrations by @jdrew82
-- [254](https://github.com/nautobot/nautobot-plugin-ssot/pull/254) - Add plugin-ssot as integration code owners by @jdrew82
-- [251](https://github.com/nautobot/nautobot-plugin-ssot/pull/251) - Cookie initialy baked by NetworkToCode Cookie Drift Manager Tool by @snaselj
-- [250](https://github.com/nautobot/nautobot-plugin-ssot/pull/250) - fixes possible error state in nautobot_ssot_duration_seconds metric by @Kircheneer
-- [260](https://github.com/nautobot/nautobot-plugin-ssot/pull/260) - Fix Documentation for Device42 by @jdrew82
-- [246](https://github.com/nautobot/nautobot-plugin-ssot/pull/246) - IPFabric integration settings updates for 2.0 by @alhogan
-- [264](https://github.com/nautobot/nautobot-plugin-ssot/pull/264) - Add check for missing job_class attribute on 1.x Jobs by @jdrew82
+- [242](https://github.com/nautobot/nautobot-app-ssot/pull/242) - Fix Infoblox import_subnets setting by @jdrew82
+- [252](https://github.com/nautobot/nautobot-app-ssot/pull/252) - Add CODEOWNERS for the various integrations by @jdrew82
+- [254](https://github.com/nautobot/nautobot-app-ssot/pull/254) - Add app-ssot as integration code owners by @jdrew82
+- [251](https://github.com/nautobot/nautobot-app-ssot/pull/251) - Cookie initialy baked by NetworkToCode Cookie Drift Manager Tool by @snaselj
+- [250](https://github.com/nautobot/nautobot-app-ssot/pull/250) - fixes possible error state in nautobot_ssot_duration_seconds metric by @Kircheneer
+- [260](https://github.com/nautobot/nautobot-app-ssot/pull/260) - Fix Documentation for Device42 by @jdrew82
+- [246](https://github.com/nautobot/nautobot-app-ssot/pull/246) - IPFabric integration settings updates for 2.0 by @alhogan
+- [264](https://github.com/nautobot/nautobot-app-ssot/pull/264) - Add check for missing job_class attribute on 1.x Jobs by @jdrew82
## v2.0.2 - 2023-11-28
## Changed
-- [229](https://github.com/nautobot/nautobot-plugin-ssot/pull/229) - Add the ability to customize queryset used on NautobotModels by @qduk
-- [259](https://github.com/nautobot/nautobot-plugin-ssot/pull/259) - add fix for issue #257 by @Renrut5
-- [280](https://github.com/nautobot/nautobot-plugin-ssot/pull/280) - fixes tests / pylint warning by @Kircheneer
-- [275](https://github.com/nautobot/nautobot-plugin-ssot/pull/275) - Fix CustomField Bug by @jdrew82
-- [282](https://github.com/nautobot/nautobot-plugin-ssot/pull/282) - Fix CloudVision SSoT Jobs by @jdrew82
-- [279](https://github.com/nautobot/nautobot-plugin-ssot/pull/279) - bug #266 by @gerardocastaldo
+- [229](https://github.com/nautobot/nautobot-app-ssot/pull/229) - Add the ability to customize queryset used on NautobotModels by @qduk
+- [259](https://github.com/nautobot/nautobot-app-ssot/pull/259) - add fix for issue #257 by @Renrut5
+- [280](https://github.com/nautobot/nautobot-app-ssot/pull/280) - fixes tests / pylint warning by @Kircheneer
+- [275](https://github.com/nautobot/nautobot-app-ssot/pull/275) - Fix CustomField Bug by @jdrew82
+- [282](https://github.com/nautobot/nautobot-app-ssot/pull/282) - Fix CloudVision SSoT Jobs by @jdrew82
+- [279](https://github.com/nautobot/nautobot-app-ssot/pull/279) - bug #266 by @gerardocastaldo
diff --git a/docs/admin/release_notes/version_2.1.md b/docs/admin/release_notes/version_2.1.md
new file mode 100644
index 000000000..bae8cc8fb
--- /dev/null
+++ b/docs/admin/release_notes/version_2.1.md
@@ -0,0 +1,22 @@
+
+# v2.1 Release Notes
+
+## v2.1.0 - 2024-01-05
+
+## Added
+
+- [287](https://github.com/nautobot/nautobot-app-ssot/pull/287) - Validate Default Settings Exist for CVP Integration And Fix DeviceRole attribute. by @jdrew82
+- [299](https://github.com/nautobot/nautobot-app-ssot/pull/299) - Ensure Device ContentType on Site LocationType, Role, and Tags in CVP by @jdrew82
+- [298](https://github.com/nautobot/nautobot-app-ssot/pull/298) - Adds a debugging guide for SSoT jobs by @Kircheneer
+- [281](https://github.com/nautobot/nautobot-app-ssot/pull/281) - Add Custom Database Parameter Loader by @Renrut5
+- [309](https://github.com/nautobot/nautobot-app-ssot/pull/309) - Support using long interface names with IPFabric by @jmcgill298
+- [313](https://github.com/nautobot/nautobot-app-ssot/pull/313) - Add support to IPFabric for additional interface media by @jmcgill298
+
+## Fixed
+
+- [301](https://github.com/nautobot/nautobot-app-ssot/pull/301) - Fix Vlan Type, Transceiver , Location and role devices, by @gerardocastaldo
+- [303](https://github.com/nautobot/nautobot-app-ssot/pull/303) - Add single network view and IP Address type support by @Dav-C
+
+## Changed
+
+- [312](https://github.com/nautobot/nautobot-app-ssot/pull/312) - Renaming work by @whitej6
diff --git a/docs/admin/uninstall.md b/docs/admin/uninstall.md
index 0314683d3..7f1a56692 100644
--- a/docs/admin/uninstall.md
+++ b/docs/admin/uninstall.md
@@ -10,10 +10,10 @@ $ pip3 uninstall nautobot-ssot
## Database Cleanup
-Prior to removing the plugin from the `nautobot_config.py`, run the following command to roll back any migration specific to this plugin.
+Prior to removing the app from the `nautobot_config.py`, run the following command to roll back any migration specific to this app.
```shell
-nautobot-server migrate nautobot_plugin_ssot zero
+nautobot-server migrate nautobot_ssot zero
```
## Remove App configuration
diff --git a/docs/admin/upgrade.md b/docs/admin/upgrade.md
index cedbf977d..838d3fb31 100644
--- a/docs/admin/upgrade.md
+++ b/docs/admin/upgrade.md
@@ -23,7 +23,7 @@ Conflicting apps list:
To prevent conflicts during `nautobot-ssot` upgrade:
- Remove conflicting applications from the `PLUGINS` section in your Nautobot configuration before enabling the latest `nautobot-ssot` version.
-- Transfer the configuration for conflicting apps to the `PLUGIN_CONFIG["nautobot_ssot"]` section of your Nautobot configuration. See `development/nautobot_config.py` for an example. Each [integration set up guide](../integrations/) contains a chapter with upgrade instructions.
+- Transfer the configuration for conflicting apps to the `PLUGIN_CONFIG["nautobot_ssot"]` section of your Nautobot configuration. See `development/nautobot_config.py` for an example. Each [integration set up guide](./integrations/index.md) contains a chapter with upgrade instructions.
- Remove conflicting applications from your project's requirements.
These steps will help prevent issues during `nautobot-ssot` upgrades. Always back up your data and thoroughly test your configuration after these changes.
diff --git a/docs/dev/debugging.md b/docs/dev/debugging.md
new file mode 100644
index 000000000..bec30c9c0
--- /dev/null
+++ b/docs/dev/debugging.md
@@ -0,0 +1,53 @@
+# Debugging SSoT Jobs
+
+When debugging SSoT jobs, it can be quite time-consuming to run the full job against the system fo record any time you make a change. As such, this page aims to describe steps to make this process easier.
+
+## Debugging Model Methods
+
+In order to debug the `create`, `update`, and `delete` methods on SSoT models, you can employ the following steps:
+
+- Start a Python interpreter in the Nautobot environment with `nautobot-server shell_plus`
+- Import your model into the interpreter (e.g. `from nautobot_ssot.integrations.aci.diffsync.models.nautobot import NautobotTenant`)
+- Test the individual method you want to test directly:
+
+```python
+from nautobot_ssot.integrations.aci.diffsync.models.nautobot import NautobotTenant
+# Note: You might need to instantiate your actual adapter and pass it in as the diffsync parameter, this may be the case
+# if you are doing logging. `unittest.MagicMock` can help here as a substitute for e.g. an actual job class.
+# If you want to test `create`
+NautobotTenant.create(diffsync=None, ids={"name": "Company A"}, attrs={"site_tag": "Something"})
+tenant = NautobotTenant(name="Company A", site_tag="Something")
+# If you want to test `update`
+tenant.update(attrs={"site_tag": "Something else"})
+# If you want to test `delete`
+tenant.delete()
+```
+
+This way you can mock the SSoT framework itself calling these methods, and even insert `breakpoint`s in places where you need to [jump in with a debugger](https://docs.python.org/3/library/pdb.html).
+
+## Debugging the Adapter(s) and the diff
+
+If you want to debug the adapters themselves, you can use a similar approach:
+
+```python
+from nautobot_ssot.integrations.aci.diffsync.adapters.aci import AciAdapter
+from nautobot_ssot.integrations.aci.diffsync.adapters.nautobot import NautobotAdapter
+# For each adapter you will probably need some kind of API client or similar piece of software.
+# This will be different depending on how your adapter is implemented. Here we just assume they exist already.
+aci_adapter = AciAdapter(client=aci_client)
+nautobot_adapter = NautobotAdapter(client=nautobot_client)
+
+# This is what the SSoT job will call to load the data from your system of record
+aci_adapter.load()
+nautobot_adapter.load()
+# At this point you can peek into the store of the adapters, the following example gives you all tenant objects loaded
+# from ACI in a list.
+tenants = aci_adapter.get_all("tenant")
+# You can also look at the diff this way.
+# Note: You will need to manually pass any flags that you are using here.
+diff = aci_adapter.diff_to(nautobot_adapter)
+# Now if you are satisifed you could even call the sync_to method to perform the actual data sync.
+aci_adapter.sync_to(nautobot_adapter)
+```
+
+Note that again you can insert `breakpoint`s in your code do dig deeper with a debugger.
diff --git a/docs/dev/dev_environment.md b/docs/dev/dev_environment.md
index f0a146264..70c474013 100644
--- a/docs/dev/dev_environment.md
+++ b/docs/dev/dev_environment.md
@@ -127,8 +127,8 @@ Each command can be executed with `invoke `. All commands support the a
flake8 Run flake8 to check that Python files adhere to its style standards.
pydocstyle Run pydocstyle to validate docstring formatting adheres to NTC defined standards.
pylint Run pylint code analysis.
- tests Run all tests for this plugin.
- unittest Run Django unit tests for the plugin.
+ tests Run all tests for this app.
+ unittest Run Django unit tests for the app.
```
## Project Overview
diff --git a/docs/dev/jobs.md b/docs/dev/jobs.md
index 01942f21a..e8037dba9 100644
--- a/docs/dev/jobs.md
+++ b/docs/dev/jobs.md
@@ -21,7 +21,7 @@ It contains 3 steps:
- Nautobot Job
```python
-# example_ssot_plugin/jobs.py
+# example_ssot_app/jobs.py
from typing import Optional
from diffsync import DiffSync
@@ -132,6 +132,22 @@ class YourSSoTNautobotAdapter(NautobotAdapter):
The `load` function is already implemented on this adapter and will automatically and recursively traverse any children relationships for you, provided the models are [defined correctly](../user/modeling.md).
+Developers are able to override the default loading of basic parameters to control how that parameter is loaded from Nautobot.
+
+This only works with basic parameters belonging to the model and does not override more complex parameters (foreign keys, custom fields, custom relationships, etc.).
+
+To override a parameter, simply add a method with the name `load_param_{param_key}` to your adapter class inheriting from `NautobotAdapter`:
+
+```python
+from nautobot_ssot.contrib import NautobotAdapter
+
+class YourSSoTNautobotAdapter(NautobotAdapter):
+ ...
+ def load_param_time_zone(self, parameter_name, database_object):
+ """Custom loader for `time_zone` parameter."""
+ return str(getattr(database_object, parameter_name))
+```
+
### Step 2.2 - Creating the Remote Adapter
Regardless of which direction you are synchronizing data in, you need to write the `load` method for the remote adapter yourself. You can find many examples of how this can be done in the `nautobot_ssot.integrations` module, which contains pre-existing integrations with remote systems.
diff --git a/docs/user/app_use_cases.md b/docs/user/app_use_cases.md
index 1b463b045..13ba9be9d 100644
--- a/docs/user/app_use_cases.md
+++ b/docs/user/app_use_cases.md
@@ -48,6 +48,14 @@ The **Sync Logs** tab shows the logs captured from DiffSync regarding the indivi
![Sync logs view](../images/sync_logs.png)
+## Management Commands
+
+### Elongate Interface Names
+
+This adds a command to the `nautobot-server` management commands to update DCIM.Interface names in Nautobot to their long form using [netutils](https://github.com/networktocode/netutils).
+This command should only be ran when the loading of the interface names in integrations system will match the long form name.
+For example, running this command, and then performing a sync from a system into Nautobot where the integration system uses different names will result in deleting the interfaces in Nautobot, and creating new ones with the name matching what is in the integration system.
+
## Screenshots
Here is a consolidated view of all the pages within the SSoT Nautobot app.
diff --git a/docs/user/external_interactions.md b/docs/user/external_interactions.md
index ff12af5c8..67aa0a988 100644
--- a/docs/user/external_interactions.md
+++ b/docs/user/external_interactions.md
@@ -8,7 +8,7 @@ This document describes external dependencies and prerequisites for this App to
## Prometheus Metrics
-Nautobot SSoT will add Prometheus metrics for multiple pieces of data that might be of interest in your environment to the `/api/plugins/capacity-metrics/app-metrics` output if the [Nautobot Capacity Metrics](https://github.com/nautobot/nautobot-plugin-capacity-metrics) app is installed and configured. The following metrics are added:
+Nautobot SSoT will add Prometheus metrics for multiple pieces of data that might be of interest in your environment to the `/api/plugins/capacity-metrics/app-metrics` output if the [Nautobot Capacity Metrics](https://github.com/nautobot/nautobot-app-capacity-metrics) app is installed and configured. The following metrics are added:
The Nautobot SSoT app has the Nautobot Capacity Metrics app as a dependency, but it is up to the admin to enable it in the `nautobot_config.py` configuration.
diff --git a/docs/user/integrations/aci.md b/docs/user/integrations/aci.md
index 4d6363729..630807d7b 100644
--- a/docs/user/integrations/aci.md
+++ b/docs/user/integrations/aci.md
@@ -1,6 +1,6 @@
# Cisco ACI SSoT Integration
-The Cisco ACI SSoT integration is built as part of the [Nautobot Single Source of Truth (SSoT)](../..tps://github.com/nautobot/nautobot-plugin-ssot) app. The SSoT app enables Nautobot to be the aggregation point for data coming from multiple systems of record (SoR).
+The Cisco ACI SSoT integration is built as part of the [Nautobot Single Source of Truth (SSoT)](https://github.com/nautobot/nautobot-app-ssot) app. The SSoT app enables Nautobot to be the aggregation point for data coming from multiple systems of record (SoR).
To accomplish the synchronization of data, the SSoT ACI integrations communicates with the Cisco ACI controller, the Application Policy Infrastructure Controller (APIC). The APIC provides a central point of administration for the ACI fabric via a web dashboard or REST API.
diff --git a/docs/user/integrations/aristacv.md b/docs/user/integrations/aristacv.md
index e688c5b6b..987bcb67c 100644
--- a/docs/user/integrations/aristacv.md
+++ b/docs/user/integrations/aristacv.md
@@ -1,6 +1,6 @@
# Arista CloudVision SSoT Integration
-The Arista CloudVision SSoT integration is built as part of the [Nautobot Single Source of Truth (SSoT)](../..tps://github.com/nautobot/nautobot-plugin-ssot) app. The SSoT app enables Nautobot to be the aggregation point for data coming from multiple systems of record (SoR).
+The Arista CloudVision SSoT integration is built as part of the [Nautobot Single Source of Truth (SSoT)](https://github.com/nautobot/nautobot-app-ssot) app. The SSoT app enables Nautobot to be the aggregation point for data coming from multiple systems of record (SoR).
From Nautobot into CloudVision, it synchronizes user device tags. From CloudVision into Nautobot, it synchronizes devices, their interfaces, associated IP addresses, and their system tags. Here is a table showing the data mappings when syncing from CloudVision.
@@ -25,7 +25,7 @@ From Nautobot into CloudVision, it synchronizes user device tags. From CloudVisi
`*` The model system tag is mapped to the device type model in Nautobot.
-`**` If the [Device Lifecycle Nautobot app](https://github.com/nautobot/nautobot-plugin-device-lifecycle-mgmt) is found to be installed, a matching Version will be created with a RelationshipAssociation connecting the device and that Version.
+`**` If the [Device Lifecycle Nautobot app](https://github.com/nautobot/nautobot-app-device-lifecycle-mgmt) is found to be installed, a matching Version will be created with a RelationshipAssociation connecting the device and that Version.
When syncing User tags from Nautobot to CloudVision the data mappings are as follows:
@@ -37,7 +37,7 @@ When syncing User tags from Nautobot to CloudVision the data mappings are as fol
## Usage
-This integration can sync data both `to` and `from` Nautobot. Once the integration has been installed successfully two new options are available under the [Nautobot Single Source of Truth (SSoT)](https://github.com/nautobot/nautobot-plugin-ssot) app.
+This integration can sync data both `to` and `from` Nautobot. Once the integration has been installed successfully two new options are available under the [Nautobot Single Source of Truth (SSoT)](https://github.com/nautobot/nautobot-app-ssot) app.
![Arista Integration](../../images/aristacv-integration.png)
diff --git a/docs/user/integrations/device42.md b/docs/user/integrations/device42.md
index e5f652d53..ea362cf9b 100644
--- a/docs/user/integrations/device42.md
+++ b/docs/user/integrations/device42.md
@@ -1,6 +1,6 @@
# Device42 SSoT Integration
-The Device42 SSoT integration is built as part of the [Nautobot Single Source of Truth (SSoT)](../..tps://github.com/nautobot/nautobot-plugin-ssot) app. The SSoT app enables Nautobot to be the aggregation point for data coming from multiple systems of record (SoR).
+The Device42 SSoT integration is built as part of the [Nautobot Single Source of Truth (SSoT)](https://github.com/nautobot/nautobot-app-ssot) app. The SSoT app enables Nautobot to be the aggregation point for data coming from multiple systems of record (SoR).
From Device42 into Nautobot, it synchronizes the following objects:
@@ -20,11 +20,11 @@ From Device42 into Nautobot, it synchronizes the following objects:
| Vendors | Providers |
| Telco Circuits | Circuits |
-`**` If the [Device Lifecycle Nautobot app](https://github.com/nautobot/nautobot-plugin-device-lifecycle-mgmt) is found to be installed, a matching Version will be created with a RelationshipAssociation connecting the Device and that Version.
+`**` If the [Device Lifecycle Nautobot app](https://github.com/nautobot/nautobot-app-device-lifecycle-mgmt) is found to be installed, a matching Version will be created with a RelationshipAssociation connecting the Device and that Version.
## Usage
-Once the plugin is installed and configured, you will be able to perform a data import from Device42 into Nautobot. From the Nautobot SSoT Dashboard view (`/plugins/ssot/`), Device42 will show as a Data Source.
+Once the app is installed and configured, you will be able to perform a data import from Device42 into Nautobot. From the Nautobot SSoT Dashboard view (`/plugins/ssot/`), Device42 will show as a Data Source.
![Dashboard View](../../images/device42_dashboard.png)
diff --git a/docs/user/integrations/ipfabric.md b/docs/user/integrations/ipfabric.md
index a0c03cd18..b81974095 100644
--- a/docs/user/integrations/ipfabric.md
+++ b/docs/user/integrations/ipfabric.md
@@ -132,7 +132,7 @@ If an object has already been updated with the tag, a warning message will be di
## ChatOps
-As part of the SSoT synchronization capabilities with IP Fabric, this integration extends the [Nautobot ChatOps app](https://github.com/nautobot/nautobot-plugin-chatops) by providing users with the ability to begin the sync job from a ChatOps command (Slack).
+As part of the SSoT synchronization capabilities with IP Fabric, this integration extends the [Nautobot ChatOps app](https://github.com/nautobot/nautobot-app-chatops) by providing users with the ability to begin the sync job from a ChatOps command (Slack).
![ssot-chatops-sync](../../images/ipfabric-chatops-ssot.png)
diff --git a/docs/user/performance.md b/docs/user/performance.md
index 130e3ed28..3e3446272 100644
--- a/docs/user/performance.md
+++ b/docs/user/performance.md
@@ -1,6 +1,6 @@
# Developing Data Source and Data Target Jobs
-A goal of this plugin is to make it relatively quick and straightforward to develop and integrate your own system-specific Data Sources and Data Targets into Nautobot with a common UI and user experience.
+A goal of this app is to make it relatively quick and straightforward to develop and integrate your own system-specific Data Sources and Data Targets into Nautobot with a common UI and user experience.
Familiarity with [DiffSync](https://diffsync.readthedocs.io/en/latest/) and with developing [Nautobot Jobs](https://nautobot.readthedocs.io/en/latest/additional-features/jobs/) is recommended.
@@ -17,7 +17,7 @@ In brief, the following general steps can be followed:
2. Define a `DiffSync` adapter class for loading initial data from Nautobot and constructing instances of each `DiffSyncModel` class to represent that data.
3. Define a `DiffSync` adapter class for loading initial data from the Data Source or Data Target system and constructing instances of the `DiffSyncModel` classes to represent that data.
-4. Develop a Job class, derived from either the `DataSource` or `DataTarget` classes provided by this plugin, and implement the adapters to populate the `self.source_adapter` and `self.target_adapter` that are used by the built-in implementation of `sync_data`. This `sync_data` method is an opinionated way of running the process including some performance data, more in [next section](#analyze-job-performance), but you could overwrite it completely or any of the key hooks that it calls:
+4. Develop a Job class, derived from either the `DataSource` or `DataTarget` classes provided by this app, and implement the adapters to populate the `self.source_adapter` and `self.target_adapter` that are used by the built-in implementation of `sync_data`. This `sync_data` method is an opinionated way of running the process including some performance data, more in [next section](#analyze-job-performance), but you could overwrite it completely or any of the key hooks that it calls:
- `self.load_source_adapter`: This is mandatory to be implemented. As an example:
@@ -41,8 +41,8 @@ In brief, the following general steps can be followed:
- `self.execute_sync`: This method is implemented by default, using the output from load_adapter methods. Only executed if it's not a `dry-run` execution.
-5. Optionally, on your Job class, also implement the `lookup_object`, `data_mappings`, and/or `config_information` APIs (to provide more information to the end user about the details of this Job), as well as the various metadata properties on your Job's `Meta` inner class. Refer to the example Jobs provided in this plugin for examples and further details.
-6. Install your Job via any of the supported Nautobot methods (installation into the `JOBS_ROOT` directory, inclusion in a Git repository, or packaging as part of a plugin) and it should automatically become available!
+5. Optionally, on your Job class, also implement the `lookup_object`, `data_mappings`, and/or `config_information` APIs (to provide more information to the end user about the details of this Job), as well as the various metadata properties on your Job's `Meta` inner class. Refer to the example Jobs provided in this app for examples and further details.
+6. Install your Job via any of the supported Nautobot methods (installation into the `JOBS_ROOT` directory, inclusion in a Git repository, or packaging as part of a app) and it should automatically become available!
## Optimizing for Execution Time
diff --git a/mkdocs.yml b/mkdocs.yml
index 6c3455f2b..7faaf8a16 100644
--- a/mkdocs.yml
+++ b/mkdocs.yml
@@ -4,7 +4,7 @@ edit_uri: "blob/develop/docs"
site_dir: "nautobot_ssot/static/nautobot_ssot/docs"
site_name: "Single Source of Truth Documentation"
site_url: "https://docs.nautobot.com/projects/ssot/en/latest/"
-repo_url: "https://github.com/nautobot/nautobot-plugin-ssot/"
+repo_url: "https://github.com/nautobot/nautobot-app-ssot/"
copyright: "Copyright © The Authors"
theme:
name: "material"
@@ -128,8 +128,9 @@ nav:
- Compatibility Matrix: "admin/compatibility_matrix.md"
- Release Notes:
- "admin/release_notes/index.md"
+ - v2.1: "admin/release_notes/version_2.1.md"
- v2.0: "admin/release_notes/version_2.0.md"
- - v1.6: "admin/release_notes/version_1.5.md"
+ - v1.6: "admin/release_notes/version_1.6.md"
- v1.5: "admin/release_notes/version_1.5.md"
- v1.4: "admin/release_notes/version_1.4.md"
- v1.3: "admin/release_notes/version_1.3.md"
@@ -139,10 +140,13 @@ nav:
- Developer Guide:
- Extending the App: "dev/extending.md"
- Developing Jobs: "dev/jobs.md"
+ - Debugging Jobs: "dev/debugging.md"
- Contributing to the App: "dev/contributing.md"
- Development Environment: "dev/dev_environment.md"
- Code Reference:
- "dev/code_reference/index.md"
+ - Package: "dev/code_reference/package.md"
+ - API: "dev/code_reference/api.md"
- Models: "dev/code_reference/models.md"
- Other classes: "dev/other_classes_reference.md"
- Nautobot Docs Home ↗︎: "https://docs.nautobot.com"
diff --git a/nautobot_ssot/__init__.py b/nautobot_ssot/__init__.py
index 2f16d8a85..aa1a5c41f 100644
--- a/nautobot_ssot/__init__.py
+++ b/nautobot_ssot/__init__.py
@@ -1,4 +1,4 @@
-"""Plugin declaration for nautobot_ssot."""
+"""App declaration for nautobot_ssot."""
import os
from importlib import metadata
@@ -35,8 +35,8 @@ def _check_for_conflicting_apps():
_check_for_conflicting_apps()
-class NautobotSSOTPluginConfig(NautobotAppConfig):
- """Plugin configuration for the nautobot_ssot plugin."""
+class NautobotSSOTAppConfig(NautobotAppConfig):
+ """App configuration for the nautobot_ssot app."""
name = "nautobot_ssot"
verbose_name = "Single Source of Truth"
@@ -128,4 +128,4 @@ def ready(self):
module.register_signals(self)
-config = NautobotSSOTPluginConfig # pylint:disable=invalid-name
+config = NautobotSSOTAppConfig # pylint:disable=invalid-name
diff --git a/nautobot_ssot/api/__init__.py b/nautobot_ssot/api/__init__.py
index 8e263c2ee..8445eb839 100644
--- a/nautobot_ssot/api/__init__.py
+++ b/nautobot_ssot/api/__init__.py
@@ -1 +1 @@
-"""REST API module for nautobot_ssot plugin."""
+"""REST API module for nautobot_ssot app."""
diff --git a/nautobot_ssot/contrib.py b/nautobot_ssot/contrib.py
index 9945cfd7a..be932b470 100644
--- a/nautobot_ssot/contrib.py
+++ b/nautobot_ssot/contrib.py
@@ -104,7 +104,12 @@ def _load_single_object(self, database_object, diffsync_model, parameter_names):
continue
# Handling of normal fields - as this is the default case, set the attribute directly.
- parameters[parameter_name] = getattr(database_object, parameter_name)
+ if hasattr(self, f"load_param_{parameter_name}"):
+ parameters[parameter_name] = getattr(self, f"load_param_{parameter_name}")(
+ parameter_name, database_object
+ )
+ else:
+ parameters[parameter_name] = getattr(database_object, parameter_name)
try:
diffsync_model = diffsync_model(**parameters)
except pydantic.ValidationError as error:
diff --git a/nautobot_ssot/integrations/aci/constant.py b/nautobot_ssot/integrations/aci/constant.py
index 108a988bf..2edec9830 100644
--- a/nautobot_ssot/integrations/aci/constant.py
+++ b/nautobot_ssot/integrations/aci/constant.py
@@ -1,4 +1,4 @@
-"""Constants for use with the ACI SSoT plugin."""
+"""Constants for use with the ACI SSoT app."""
from django.conf import settings
diff --git a/nautobot_ssot/integrations/aci/diffsync/models/base.py b/nautobot_ssot/integrations/aci/diffsync/models/base.py
index 3238d8cbe..258f474cb 100644
--- a/nautobot_ssot/integrations/aci/diffsync/models/base.py
+++ b/nautobot_ssot/integrations/aci/diffsync/models/base.py
@@ -1,4 +1,4 @@
-"""Base Shared Models for Cisco ACI integration with SSoT plugin."""
+"""Base Shared Models for Cisco ACI integration with SSoT app."""
from typing import List, Optional
from diffsync import DiffSyncModel
diff --git a/nautobot_ssot/integrations/aci/diffsync/models/nautobot.py b/nautobot_ssot/integrations/aci/diffsync/models/nautobot.py
index 3aa35c7cc..980eb5814 100644
--- a/nautobot_ssot/integrations/aci/diffsync/models/nautobot.py
+++ b/nautobot_ssot/integrations/aci/diffsync/models/nautobot.py
@@ -1,4 +1,4 @@
-"""Nautobot Models for Cisco ACI integration with SSoT plugin."""
+"""Nautobot Models for Cisco ACI integration with SSoT app."""
import logging
from diffsync.exceptions import ObjectNotCreated
diff --git a/nautobot_ssot/integrations/aci/jobs.py b/nautobot_ssot/integrations/aci/jobs.py
index eabbedcd7..3b68984b9 100644
--- a/nautobot_ssot/integrations/aci/jobs.py
+++ b/nautobot_ssot/integrations/aci/jobs.py
@@ -1,4 +1,4 @@
-"""Jobs for ACI SSoT plugin."""
+"""Jobs for ACI SSoT app."""
from django.templatetags.static import static
from django.urls import reverse
from diffsync import DiffSyncFlags
diff --git a/nautobot_ssot/integrations/aci/signals.py b/nautobot_ssot/integrations/aci/signals.py
index ee281f78c..b53cae1ee 100644
--- a/nautobot_ssot/integrations/aci/signals.py
+++ b/nautobot_ssot/integrations/aci/signals.py
@@ -87,14 +87,14 @@ def device_custom_fields(apps, **kwargs):
"type": CustomFieldTypeChoices.TYPE_INTEGER,
"label": "Cisco ACI Pod ID",
"filter_logic": "loose",
- "description": "PodID added by SSoT plugin",
+ "description": "PodID added by SSoT app",
},
{
"key": "aci_node_id",
"type": CustomFieldTypeChoices.TYPE_INTEGER,
"label": "Cisco ACI Node ID",
"filter_logic": "loose",
- "description": "NodeID added by SSoT plugin",
+ "description": "NodeID added by SSoT app",
},
]:
field, _ = CustomField.objects.get_or_create(key=device_cf_dict["key"], defaults=device_cf_dict)
@@ -114,28 +114,28 @@ def interface_custom_fields(apps, **kwargs):
"type": CustomFieldTypeChoices.TYPE_TEXT,
"label": "Optic Vendor",
"filter_logic": "loose",
- "description": "Optic vendor added by SSoT plugin",
+ "description": "Optic vendor added by SSoT app",
},
{
"key": "gbic_type",
"type": CustomFieldTypeChoices.TYPE_TEXT,
"label": "Optic Type",
"filter_logic": "loose",
- "description": "Optic type added by SSoT plugin",
+ "description": "Optic type added by SSoT app",
},
{
"key": "gbic_sn",
"type": CustomFieldTypeChoices.TYPE_TEXT,
"label": "Optic S/N",
"filter_logic": "loose",
- "description": "Optic S/N added by SSoT plugin",
+ "description": "Optic S/N added by SSoT app",
},
{
"key": "gbic_model",
"type": CustomFieldTypeChoices.TYPE_TEXT,
"label": "Optic Model",
"filter_logic": "loose",
- "description": "Optic Model added by SSoT plugin",
+ "description": "Optic Model added by SSoT app",
},
]:
field, _ = CustomField.objects.get_or_create(key=interface_cf_dict["key"], defaults=interface_cf_dict)
diff --git a/nautobot_ssot/integrations/aristacv/constant.py b/nautobot_ssot/integrations/aristacv/constant.py
index 39b9b1cf8..44adcbd0d 100644
--- a/nautobot_ssot/integrations/aristacv/constant.py
+++ b/nautobot_ssot/integrations/aristacv/constant.py
@@ -14,6 +14,7 @@ def _read_settings() -> dict:
"xcvr1000BaseT": "1000base-t",
"xcvr10GBaseSr": "10gbase-x-xfp",
"xcvr10GBaseLr": "10gbase-x-xfp",
+ "xcvr10GBaseT": "10gbase-t",
"1000BASE-SX": "1000base-x-gbic",
"1000BASE-LX": "1000base-x-gbic",
"1000BASE-T": "1000base-t",
diff --git a/nautobot_ssot/integrations/aristacv/diffsync/adapters/cloudvision.py b/nautobot_ssot/integrations/aristacv/diffsync/adapters/cloudvision.py
index a904b4208..b91da7c07 100644
--- a/nautobot_ssot/integrations/aristacv/diffsync/adapters/cloudvision.py
+++ b/nautobot_ssot/integrations/aristacv/diffsync/adapters/cloudvision.py
@@ -55,7 +55,9 @@ def load_devices(self):
self.add(new_cvp)
except ObjectAlreadyExists as err:
self.job.logger.warning(f"Error attempting to add CloudVision device. {err}")
- for dev in cloudvision.get_devices(client=self.conn.comm_channel):
+
+ for index, dev in enumerate(cloudvision.get_devices(client=self.conn.comm_channel), start=1):
+ self.job.logger.info(f"Loading {index}° device")
if dev["hostname"] != "":
new_device = self.device(
name=dev["hostname"],
@@ -94,13 +96,19 @@ def load_interfaces(self, device):
f"Unable to determine chassis type for {device.name} so will be unable to retrieve interfaces."
)
return None
+
if self.job.debug:
self.job.logger.debug(f"Device being loaded: {device.name}. Port: {port_info}.")
+
for port in port_info:
if self.job.debug:
self.job.logger.debug(f"Port {port['interface']} being loaded for {device.name}.")
- port_mode = cloudvision.get_interface_mode(client=self.conn, dId=device.serial, interface=port)
- transceiver = cloudvision.get_interface_transceiver(client=self.conn, dId=device.serial, interface=port)
+
+ port_mode = cloudvision.get_interface_mode(client=self.conn, dId=device.serial, interface=port["interface"])
+ transceiver = cloudvision.get_interface_transceiver(
+ client=self.conn, dId=device.serial, interface=port["interface"]
+ )
+
if transceiver == "Unknown":
# Breakout transceivers, ie 40G -> 4x10G, shows up as 4 interfaces and requires looking at base interface to find transceiver, ie Ethernet1 if Ethernet1/1
base_port_name = re.sub(r"/\d", "", port["interface"])
@@ -128,6 +136,10 @@ def load_interfaces(self, device):
try:
self.add(new_port)
device.add_child(new_port)
+ if self.job.debug:
+ self.job.logger.debug(
+ f"""Added {port['interface']} for {device.name}. \n description: {port_description}\n enabled: {port['enabled']}\n status: {port_status}\n transceiver: {transceiver}\n port_type: {port_type}\n mode: {port_mode}"""
+ )
except ObjectAlreadyExists as err:
self.job.logger.warning(
f"Duplicate port {port['interface']} found for {device.name} and ignored. {err}"
@@ -153,7 +165,7 @@ def load_ip_addresses(self, dev: device):
mode="access",
mtu=65535,
port_type=cloudvision.get_port_type(port_info={"interface": intf["interface"]}, transceiver=""),
- status="active",
+ status="Active",
uuid=None,
)
self.add(new_port)
diff --git a/nautobot_ssot/integrations/aristacv/diffsync/models/__init__.py b/nautobot_ssot/integrations/aristacv/diffsync/models/__init__.py
index 2963b32ef..36270223c 100644
--- a/nautobot_ssot/integrations/aristacv/diffsync/models/__init__.py
+++ b/nautobot_ssot/integrations/aristacv/diffsync/models/__init__.py
@@ -1 +1 @@
-"""DiffSync models and adapters for the AristaCV SSoT plugin."""
+"""DiffSync models and adapters for the AristaCV SSoT app."""
diff --git a/nautobot_ssot/integrations/aristacv/diffsync/models/nautobot.py b/nautobot_ssot/integrations/aristacv/diffsync/models/nautobot.py
index a41342f61..95d9f2bfd 100644
--- a/nautobot_ssot/integrations/aristacv/diffsync/models/nautobot.py
+++ b/nautobot_ssot/integrations/aristacv/diffsync/models/nautobot.py
@@ -20,7 +20,7 @@
LIFECYCLE_MGMT = True
except ImportError:
- print("Device Lifecycle plugin isn't installed so will revert to CustomField for OS version.")
+ print("Device Lifecycle app isn't installed so will revert to CustomField for OS version.")
LIFECYCLE_MGMT = False
@@ -78,12 +78,13 @@ def create(cls, diffsync, ids, attrs):
new_device = OrmDevice(
status=OrmStatus.objects.get(name=attrs["status"]),
device_type=device_type_object,
- device_role=role,
+ role=role,
platform=platform,
- site=site,
+ location=site,
name=ids["name"],
serial=attrs["serial"] if attrs.get("serial") else "",
)
+
if APP_SETTINGS.get("apply_import_tag", APPLY_IMPORT_TAG):
import_tag = nautobot.verify_import_tag()
new_device.tags.add(import_tag)
@@ -122,7 +123,7 @@ def update(self, attrs):
def delete(self):
"""Delete device object in Nautobot."""
if APP_SETTINGS.get("delete_devices_on_sync", DEFAULT_DELETE_DEVICES_ON_SYNC):
- self.diffsync.job.logger.warning(f"Device {self.name} will be deleted per plugin settings.")
+ self.diffsync.job.logger.warning(f"Device {self.name} will be deleted per app settings.")
device = OrmDevice.objects.get(id=self.uuid)
device.delete()
super().delete()
@@ -130,7 +131,7 @@ def delete(self):
@staticmethod
def _add_software_lcm(platform: str, version: str):
- """Add OS Version as SoftwareLCM if Device Lifecycle Plugin found."""
+ """Add OS Version as SoftwareLCM if Device Lifecycle App found."""
_platform = OrmPlatform.objects.get(name=platform)
try:
os_ver = SoftwareLCM.objects.get(device_platform=_platform, version=version)
@@ -145,7 +146,7 @@ def _add_software_lcm(platform: str, version: str):
@staticmethod
def _assign_version_to_device(diffsync, device, software_lcm):
"""Add Relationship between Device and SoftwareLCM."""
- software_relation = OrmRelationship.objects.get(name="Software on Device")
+ software_relation = OrmRelationship.objects.get(label="Software on Device")
relations = device.get_relationships()
for _, relationships in relations.items():
for relationship, queryset in relationships.items():
diff --git a/nautobot_ssot/integrations/aristacv/jobs.py b/nautobot_ssot/integrations/aristacv/jobs.py
index 6556d549d..507ba2741 100644
--- a/nautobot_ssot/integrations/aristacv/jobs.py
+++ b/nautobot_ssot/integrations/aristacv/jobs.py
@@ -1,5 +1,5 @@
# pylint: disable=invalid-name,too-few-public-methods
-"""Jobs for CloudVision integration with SSoT plugin."""
+"""Jobs for CloudVision integration with SSoT app."""
from django.templatetags.static import static
from django.urls import reverse
@@ -18,6 +18,20 @@
name = "SSoT - Arista CloudVision" # pylint: disable=invalid-name
+class MissingConfigSetting(Exception):
+ """Exception raised for missing configuration settings.
+
+ Attributes:
+ message (str): Returned explanation of Error.
+ """
+
+ def __init__(self, setting):
+ """Initialize Exception with Setting that is missing and message."""
+ self.setting = setting
+ self.message = f"Missing configuration setting - {setting}!"
+ super().__init__(self.message)
+
+
class CloudVisionDataSource(DataSource, Job): # pylint: disable=abstract-method
"""CloudVision SSoT Data Source."""
@@ -84,6 +98,16 @@ def data_mappings(cls):
def load_source_adapter(self):
"""Load data from CloudVision into DiffSync models."""
+ if not APP_SETTINGS.get("from_cloudvision_default_site"):
+ self.logger.error(
+ "App setting `aristacv_from_cloudvision_default_site` is not defined. This setting is required for the App to function."
+ )
+ raise MissingConfigSetting(setting="aristacv_from_cloudvision_default_site")
+ if not APP_SETTINGS.get("from_cloudvision_default_device_role"):
+ self.logger.error(
+ "App setting `aristacv_from_cloudvision_default_device_role` is not defined. This setting is required for the App to function."
+ )
+ raise MissingConfigSetting(setting="aristacv_from_cloudvision_default_device_role")
if self.debug:
if APP_SETTINGS.get("delete_devices_on_sync"):
self.logger.warning(
diff --git a/nautobot_ssot/integrations/aristacv/utils/cloudvision.py b/nautobot_ssot/integrations/aristacv/utils/cloudvision.py
index f7644dc5d..2e0361895 100644
--- a/nautobot_ssot/integrations/aristacv/utils/cloudvision.py
+++ b/nautobot_ssot/integrations/aristacv/utils/cloudvision.py
@@ -548,6 +548,8 @@ def get_interface_transceiver(client: CloudvisionApi, dId: str, interface: str):
"mediaType"
):
return notif["updates"]["actualIdEepromContents"]["mediaType"]
+ if notif["updates"].get("mediaType"):
+ return notif["updates"]["mediaType"]["Name"]
if notif["updates"].get("localMediaType"):
return notif["updates"]["localMediaType"]["Name"]
return "Unknown"
diff --git a/nautobot_ssot/integrations/aristacv/utils/nautobot.py b/nautobot_ssot/integrations/aristacv/utils/nautobot.py
index d0b15bc71..4de33f6a6 100644
--- a/nautobot_ssot/integrations/aristacv/utils/nautobot.py
+++ b/nautobot_ssot/integrations/aristacv/utils/nautobot.py
@@ -1,7 +1,7 @@
"""Utility functions for Nautobot ORM."""
import re
-
-from nautobot.dcim.models import DeviceType, Location, LocationType, Manufacturer
+from django.contrib.contenttypes.models import ContentType
+from nautobot.dcim.models import Device, DeviceType, Location, LocationType, Manufacturer
from nautobot.extras.models import Role, Status, Tag, Relationship
from nautobot_ssot.integrations.aristacv.constant import APP_SETTINGS
@@ -11,17 +11,18 @@
LIFECYCLE_MGMT = True
except ImportError:
- print("Device Lifecycle plugin isn't installed so will revert to CustomField for OS version.")
+ print("Device Lifecycle app isn't installed so will revert to CustomField for OS version.")
LIFECYCLE_MGMT = False
def verify_site(site_name):
- """Verifies whether site in plugin config is created. If not, creates site.
+ """Verifies whether site in app config is created. If not, creates site.
Args:
site_name (str): Name of the site.
"""
loc_type = LocationType.objects.get_or_create(name="Site")[0]
+ loc_type.content_types.add(ContentType.objects.get_for_model(Device))
try:
site_obj = Location.objects.get(name=site_name, location_type=loc_type)
except Location.DoesNotExist:
@@ -58,7 +59,8 @@ def verify_device_role_object(role_name, role_color):
try:
role_obj = Role.objects.get(name=role_name)
except Role.DoesNotExist:
- role_obj = Role(name=role_name, color=role_color)
+ role_obj = Role.objects.create(name=role_name, color=role_color)
+ role_obj.content_types.add(ContentType.objects.get_for_model(Device))
role_obj.validated_save()
return role_obj
@@ -68,7 +70,8 @@ def verify_import_tag():
try:
import_tag = Tag.objects.get(name="cloudvision_imported")
except Tag.DoesNotExist:
- import_tag = Tag(name="cloudvision_imported", color="ff0000")
+ import_tag = Tag.objects.create(name="cloudvision_imported", color="ff0000")
+ import_tag.content_types.add(ContentType.objects.get_for_model(Device))
import_tag.validated_save()
return import_tag
diff --git a/nautobot_ssot/integrations/device42/diffsync/adapters/device42.py b/nautobot_ssot/integrations/device42/diffsync/adapters/device42.py
index fa9a185e2..faf4c93d5 100644
--- a/nautobot_ssot/integrations/device42/diffsync/adapters/device42.py
+++ b/nautobot_ssot/integrations/device42/diffsync/adapters/device42.py
@@ -168,7 +168,7 @@ def get_building_for_device(self, dev_record: dict) -> str:
"""Method to determine the Building (Site) for a Device.
Args:
- dev_record (dict): Dictionary of Device information from Device42. Needs to have name, customer, and building keys depending upon enabled plugin settings.
+ dev_record (dict): Dictionary of Device information from Device42. Needs to have name, customer, and building keys depending upon enabled app settings.
Returns:
str: Slugified version of the Building (Site) for a Device.
diff --git a/nautobot_ssot/integrations/device42/diffsync/adapters/nautobot.py b/nautobot_ssot/integrations/device42/diffsync/adapters/nautobot.py
index 861a1ad82..acdd759d1 100644
--- a/nautobot_ssot/integrations/device42/diffsync/adapters/nautobot.py
+++ b/nautobot_ssot/integrations/device42/diffsync/adapters/nautobot.py
@@ -33,7 +33,7 @@
LIFECYCLE_MGMT = True
except ImportError:
- print("Device Lifecycle plugin isn't installed so will revert to CustomField for OS version.")
+ print("Device Lifecycle app isn't installed so will revert to CustomField for OS version.")
LIFECYCLE_MGMT = False
diff --git a/nautobot_ssot/integrations/device42/diffsync/models/nautobot/dcim.py b/nautobot_ssot/integrations/device42/diffsync/models/nautobot/dcim.py
index 6805ca085..67ab4c58b 100644
--- a/nautobot_ssot/integrations/device42/diffsync/models/nautobot/dcim.py
+++ b/nautobot_ssot/integrations/device42/diffsync/models/nautobot/dcim.py
@@ -41,7 +41,7 @@
LIFECYCLE_MGMT = True
except ImportError:
- print("Device Lifecycle plugin isn't installed so will revert to CustomField for OS version.")
+ print("Device Lifecycle app isn't installed so will revert to CustomField for OS version.")
LIFECYCLE_MGMT = False
@@ -612,7 +612,7 @@ def delete(self):
@staticmethod
def _add_software_lcm(diffsync: DataSource, os: str, version: str, manufacturer: UUID):
- """Add OS Version as SoftwareLCM if Device Lifecycle Plugin found."""
+ """Add OS Version as SoftwareLCM if Device Lifecycle App found."""
_platform = nautobot.verify_platform(diffsync=diffsync, platform_name=os, manu=manufacturer)
try:
os_ver = diffsync.softwarelcm_map[os][version]
diff --git a/nautobot_ssot/integrations/device42/jobs.py b/nautobot_ssot/integrations/device42/jobs.py
index 06638b7ed..8e54ac187 100644
--- a/nautobot_ssot/integrations/device42/jobs.py
+++ b/nautobot_ssot/integrations/device42/jobs.py
@@ -1,5 +1,5 @@
# pylint: disable=too-few-public-methods
-"""Jobs for Device42 integration with SSoT plugin."""
+"""Jobs for Device42 integration with SSoT app."""
from django.templatetags.static import static
from django.urls import reverse
diff --git a/nautobot_ssot/integrations/device42/utils/device42.py b/nautobot_ssot/integrations/device42/utils/device42.py
index 5c24a79df..af801cb62 100644
--- a/nautobot_ssot/integrations/device42/utils/device42.py
+++ b/nautobot_ssot/integrations/device42/utils/device42.py
@@ -150,7 +150,7 @@ def find_device_role_from_tags(tag_list: List[str]) -> str:
tag_list (List[str]): List of Tags as strings to search.
Returns:
- str: The Default device role defined in plugin settings.
+ str: The Default device role defined in app settings.
"""
_prepend = PLUGIN_CFG.get("device42_role_prepend")
if _prepend:
diff --git a/nautobot_ssot/integrations/device42/utils/nautobot.py b/nautobot_ssot/integrations/device42/utils/nautobot.py
index c8ee00c77..dd4307ef2 100644
--- a/nautobot_ssot/integrations/device42/utils/nautobot.py
+++ b/nautobot_ssot/integrations/device42/utils/nautobot.py
@@ -20,7 +20,7 @@
LIFECYCLE_MGMT = True
except ImportError:
- print("Device Lifecycle plugin isn't installed so will revert to CustomField for OS version.")
+ print("Device Lifecycle app isn't installed so will revert to CustomField for OS version.")
LIFECYCLE_MGMT = False
@@ -297,7 +297,7 @@ def determine_vc_position(vc_map: dict, virtual_chassis: str, device_name: str)
def get_dlc_version_map():
"""Method to create nested dictionary of Software versions mapped to their ID along with Platform.
- This should only be used if the Device Lifecycle plugin is found to be installed.
+ This should only be used if the Device Lifecycle app is found to be installed.
Returns:
dict: Nested dictionary of versions mapped to their ID and to their Platform.
@@ -313,7 +313,7 @@ def get_dlc_version_map():
def get_cf_version_map():
"""Method to create nested dictionary of Software versions mapped to their ID along with Platform.
- This should only be used if the Device Lifecycle plugin is not found. It will instead use custom field "OS Version".
+ This should only be used if the Device Lifecycle app is not found. It will instead use custom field "OS Version".
Returns:
dict: Nested dictionary of versions mapped to their ID and to their Platform.
diff --git a/nautobot_ssot/integrations/infoblox/constant.py b/nautobot_ssot/integrations/infoblox/constant.py
index fbad5e369..f4ade4983 100644
--- a/nautobot_ssot/integrations/infoblox/constant.py
+++ b/nautobot_ssot/integrations/infoblox/constant.py
@@ -1,8 +1,8 @@
-"""Constants for use with the Infoblox SSoT plugin."""
+"""Constants for use with the Infoblox SSoT app."""
from django.conf import settings
-def _read_plugin_config():
+def _read_app_config():
"""Provides backward compatible object after integrating into `nautobot_ssot` App."""
config = settings.PLUGINS_CONFIG["nautobot_ssot"]
@@ -12,6 +12,7 @@ def _read_plugin_config():
"NAUTOBOT_INFOBLOX_PASSWORD": config["infoblox_password"],
"NAUTOBOT_INFOBLOX_VERIFY_SSL": config["infoblox_verify_ssl"],
"NAUTOBOT_INFOBLOX_WAPI_VERSION": config["infoblox_wapi_version"],
+ "NAUTOBOT_INFOBLOX_NETWORK_VIEW": config["infoblox_network_view"],
"enable_sync_to_infoblox": config["infoblox_enable_sync_to_infoblox"],
"enable_rfc1918_network_containers": config["infoblox_enable_rfc1918_network_containers"],
"default_status": config["infoblox_default_status"],
@@ -26,5 +27,5 @@ def _read_plugin_config():
# Import config vars from nautobot_config.py
-PLUGIN_CFG = _read_plugin_config()
+PLUGIN_CFG = _read_app_config()
TAG_COLOR = "40bfae"
diff --git a/nautobot_ssot/integrations/infoblox/diffsync/__init__.py b/nautobot_ssot/integrations/infoblox/diffsync/__init__.py
index 02655518c..9aca9c3f9 100644
--- a/nautobot_ssot/integrations/infoblox/diffsync/__init__.py
+++ b/nautobot_ssot/integrations/infoblox/diffsync/__init__.py
@@ -1 +1 @@
-"""DiffSync models and adapters for the Infoblox SSoT plugin."""
+"""DiffSync models and adapters for the Infoblox SSoT app."""
diff --git a/nautobot_ssot/integrations/infoblox/diffsync/adapters/infoblox.py b/nautobot_ssot/integrations/infoblox/diffsync/adapters/infoblox.py
index a990ef790..e3ef25b88 100644
--- a/nautobot_ssot/integrations/infoblox/diffsync/adapters/infoblox.py
+++ b/nautobot_ssot/integrations/infoblox/diffsync/adapters/infoblox.py
@@ -1,8 +1,9 @@
-"""Infoblox Adapter for Infoblox integration with SSoT plugin."""
+"""Infoblox Adapter for Infoblox integration with SSoT app."""
import re
from diffsync import DiffSync
from diffsync.enum import DiffSyncFlags
+from diffsync.exceptions import ObjectAlreadyExists
from nautobot.extras.plugins.exceptions import PluginImproperlyConfigured
from nautobot_ssot.integrations.infoblox.constant import PLUGIN_CFG
from nautobot_ssot.integrations.infoblox.utils.client import get_default_ext_attrs, get_dns_name
@@ -82,7 +83,14 @@ def load_prefixes(self):
ext_attrs={**default_ext_attrs, **pf_ext_attrs},
vlans=build_vlan_map(vlans=_pf["vlans"]) if _pf.get("vlans") else {},
)
- self.add(new_pf)
+ try:
+ self.add(new_pf)
+ except ObjectAlreadyExists:
+ self.job.logger.warning(
+ f"Duplicate prefix found: {new_pf}. Duplicate prefixes are not supported, "
+ "and only the first occurrence will be included in the sync. To load data "
+ "from a single Network View, use the 'infoblox_network_view' setting."
+ )
def load_ipaddresses(self):
"""Load InfobloxIPAddress DiffSync model."""
@@ -100,6 +108,7 @@ def load_ipaddresses(self):
prefix_length=prefix_length,
dns_name=dns_name,
status=self.conn.get_ipaddr_status(_ip),
+ ip_addr_type=self.conn.get_ipaddr_type(_ip),
description=_ip["comment"],
ext_attrs={**default_ext_attrs, **ip_ext_attrs},
)
diff --git a/nautobot_ssot/integrations/infoblox/diffsync/adapters/nautobot.py b/nautobot_ssot/integrations/infoblox/diffsync/adapters/nautobot.py
index e9f67c6bd..7aa21f238 100644
--- a/nautobot_ssot/integrations/infoblox/diffsync/adapters/nautobot.py
+++ b/nautobot_ssot/integrations/infoblox/diffsync/adapters/nautobot.py
@@ -30,7 +30,7 @@ class NautobotMixin:
def tag_involved_objects(self, target):
"""Tag all objects that were successfully synced to the target."""
- # The ssot_synced_to_infoblox tag *should* have been created automatically during plugin installation
+ # The ssot_synced_to_infoblox tag *should* have been created automatically during app installation
# (see nautobot_ssot/integrations/infoblox/signals.py) but maybe a user deleted it inadvertently, so be safe:
tag, _ = Tag.objects.get_or_create(
name="SSoT Synced to Infoblox",
@@ -175,6 +175,7 @@ def load_ipaddresses(self):
address=addr,
prefix=str(prefix),
status=ipaddr.status.name if ipaddr.status else None,
+ ip_addr_type=ipaddr.type,
prefix_length=prefix.prefix_length if prefix else ipaddr.prefix_length,
dns_name=ipaddr.dns_name,
description=ipaddr.description,
diff --git a/nautobot_ssot/integrations/infoblox/diffsync/models/base.py b/nautobot_ssot/integrations/infoblox/diffsync/models/base.py
index f3b9d21f4..f8872cdf9 100644
--- a/nautobot_ssot/integrations/infoblox/diffsync/models/base.py
+++ b/nautobot_ssot/integrations/infoblox/diffsync/models/base.py
@@ -1,4 +1,4 @@
-"""Base Shared Models for Infoblox integration with SSoT plugin."""
+"""Base Shared Models for Infoblox integration with SSoT app."""
import uuid
from typing import Optional
from diffsync import DiffSyncModel
@@ -53,13 +53,14 @@ class IPAddress(DiffSyncModel):
_modelname = "ipaddress"
_identifiers = ("address", "prefix", "prefix_length")
- _attributes = ("description", "dns_name", "status", "ext_attrs")
+ _attributes = ("description", "dns_name", "status", "ip_addr_type", "ext_attrs")
address: str
dns_name: str
prefix: str
prefix_length: int
status: Optional[str]
+ ip_addr_type: Optional[str]
description: Optional[str]
ext_attrs: Optional[dict]
pk: Optional[uuid.UUID] = None
diff --git a/nautobot_ssot/integrations/infoblox/diffsync/models/infoblox.py b/nautobot_ssot/integrations/infoblox/diffsync/models/infoblox.py
index 319f382e9..e52056911 100644
--- a/nautobot_ssot/integrations/infoblox/diffsync/models/infoblox.py
+++ b/nautobot_ssot/integrations/infoblox/diffsync/models/infoblox.py
@@ -1,4 +1,4 @@
-"""Infoblox Models for Infoblox integration with SSoT plugin."""
+"""Infoblox Models for Infoblox integration with SSoT app."""
from requests.exceptions import HTTPError
from nautobot_ssot.integrations.infoblox.diffsync.models.base import Network, IPAddress, Vlan, VlanView
diff --git a/nautobot_ssot/integrations/infoblox/diffsync/models/nautobot.py b/nautobot_ssot/integrations/infoblox/diffsync/models/nautobot.py
index 988de2ffb..de5bd8ed5 100644
--- a/nautobot_ssot/integrations/infoblox/diffsync/models/nautobot.py
+++ b/nautobot_ssot/integrations/infoblox/diffsync/models/nautobot.py
@@ -1,11 +1,11 @@
-"""Nautobot Models for Infoblox integration with SSoT plugin."""
+"""Nautobot Models for Infoblox integration with SSoT app."""
from django.contrib.contenttypes.models import ContentType
from django.core.exceptions import ValidationError
from django.utils.text import slugify
from nautobot.extras.choices import CustomFieldTypeChoices
from nautobot.extras.models import RelationshipAssociation as OrmRelationshipAssociation
from nautobot.extras.models import CustomField as OrmCF
-from nautobot.ipam.choices import IPAddressRoleChoices
+from nautobot.ipam.choices import IPAddressRoleChoices, IPAddressTypeChoices
from nautobot.ipam.models import IPAddress as OrmIPAddress
from nautobot.ipam.models import Prefix as OrmPrefix
from nautobot.ipam.models import VLAN as OrmVlan
@@ -15,7 +15,7 @@
from nautobot_ssot.integrations.infoblox.utils.nautobot import get_prefix_vlans
-def process_ext_attrs(diffsync, obj: object, extattrs: dict):
+def process_ext_attrs(diffsync, obj: object, extattrs: dict): # pylint: disable=too-many-branches
"""Process Extensibility Attributes into Custom Fields or link to found objects.
Args:
@@ -23,7 +23,7 @@ def process_ext_attrs(diffsync, obj: object, extattrs: dict):
obj (object): The object that's being created or updated and needs processing.
extattrs (dict): The Extensibility Attributes to be analyzed and applied to passed `prefix`.
"""
- for attr, attr_value in extattrs.items():
+ for attr, attr_value in extattrs.items(): # pylint: disable=too-many-nested-blocks
if attr_value:
if attr.lower() in ["site", "facility", "location"]:
try:
@@ -32,13 +32,28 @@ def process_ext_attrs(diffsync, obj: object, extattrs: dict):
diffsync.job.logger.warning(
f"Unable to find Location {attr_value} for {obj} found in Extensibility Attributes '{attr}'. {err}"
)
- if attr.lower() == "vrf":
- try:
- obj.vrfs.add(diffsync.vrf_map[attr_value])
- except KeyError as err:
+ except TypeError as err:
diffsync.job.logger.warning(
- f"Unable to find VRF {attr_value} for {obj} found in Extensibility Attributes '{attr}'. {err}"
+ f"Cannot set location values {attr_value} for {obj}. Multiple locations are assigned "
+ f"in Extensibility Attributes '{attr}', but multiple location assignments are not "
+ f"supported by Nautobot. {err}"
)
+ if attr.lower() == "vrf":
+ if isinstance(attr_value, list):
+ for vrf in attr_value:
+ try:
+ obj.vrfs.add(diffsync.vrf_map[vrf])
+ except KeyError as err:
+ diffsync.job.logger.warning(
+ f"Unable to find VRF {vrf} for {obj} found in Extensibility Attributes '{attr}'. {err}"
+ )
+ else:
+ try:
+ obj.vrfs.add(diffsync.vrf_map[attr_value])
+ except KeyError as err:
+ diffsync.job.logger.warning(
+ f"Unable to find VRF {attr_value} for {obj} found in Extensibility Attributes '{attr}'. {err}"
+ )
if "role" in attr.lower():
if isinstance(obj, OrmIPAddress) and attr_value.lower() in IPAddressRoleChoices.as_dict():
obj.role = attr_value.lower()
@@ -49,7 +64,12 @@ def process_ext_attrs(diffsync, obj: object, extattrs: dict):
diffsync.job.logger.warning(
f"Unable to find Role {attr_value} for {obj} found in Extensibility Attributes '{attr}'. {err}"
)
-
+ except TypeError as err:
+ diffsync.job.logger.warning(
+ f"Cannot set role values {attr_value} for {obj}. Multiple roles are assigned "
+ f"in Extensibility Attributes '{attr}', but multiple role assignments are not "
+ f"supported by Nautobot. {err}"
+ )
if attr.lower() in ["tenant", "dept", "department"]:
try:
obj.tenant_id = diffsync.tenant_map[attr_value]
@@ -57,8 +77,14 @@ def process_ext_attrs(diffsync, obj: object, extattrs: dict):
diffsync.job.logger.warning(
f"Unable to find Tenant {attr_value} for {obj} found in Extensibility Attributes '{attr}'. {err}"
)
+ except TypeError as err:
+ diffsync.job.logger.warning(
+ f"Cannot set tenant values {attr_value} for {obj}. Multiple tenants are assigned "
+ f"in Extensibility Attributes '{attr}', but multiple tenant assignments are not "
+ f"supported by Nautobot. {err}"
+ )
_cf_dict = {
- "key": slugify(attr),
+ "key": slugify(attr).replace("-", "_"),
"type": CustomFieldTypeChoices.TYPE_TEXT,
"label": attr,
}
@@ -175,10 +201,18 @@ def create(cls, diffsync, ids, attrs):
except KeyError:
status = diffsync.status_map[PLUGIN_CFG.get("default_status", "Active")]
addr = f"{ids['address']}/{ids['prefix_length']}"
- diffsync.job.logger.debug(f"Creating IP Address {addr}")
+ if attrs.get("ip_addr_type"):
+ if attrs["ip_addr_type"].lower() in IPAddressTypeChoices.as_dict():
+ ip_addr_type = attrs["ip_addr_type"].lower()
+ else:
+ diffsync.logger.warning(f"unable to determine IPAddress Type for {addr}, defaulting to 'Host'")
+ ip_addr_type = "host"
+ if diffsync.job.debug:
+ diffsync.job.logger.debug(f"Creating IP Address {addr}")
_ip = OrmIPAddress(
address=addr,
status_id=status,
+ type=ip_addr_type,
description=attrs.get("description", ""),
dns_name=attrs.get("dns_name", ""),
parent_id=diffsync.prefix_map[ids["prefix"]],
@@ -204,6 +238,11 @@ def update(self, attrs):
except KeyError:
status = self.diffsync.status_map[PLUGIN_CFG.get("default_status", "Active")]
_ipaddr.status_id = status
+ if attrs.get("ip_addr_type"):
+ if attrs["ip_addr_type"].lower() in IPAddressTypeChoices.as_dict():
+ _ipaddr.type = attrs["ip_addr_type"].lower()
+ else:
+ _ipaddr.type = "host"
if attrs.get("description"):
_ipaddr.description = attrs["description"]
if attrs.get("dns_name"):
diff --git a/nautobot_ssot/integrations/infoblox/jobs.py b/nautobot_ssot/integrations/infoblox/jobs.py
index ede92ccdd..64578d187 100644
--- a/nautobot_ssot/integrations/infoblox/jobs.py
+++ b/nautobot_ssot/integrations/infoblox/jobs.py
@@ -1,4 +1,4 @@
-"""Jobs for Infoblox integration with SSoT plugin."""
+"""Jobs for Infoblox integration with SSoT app."""
from diffsync.enum import DiffSyncFlags
from django.templatetags.static import static
diff --git a/nautobot_ssot/integrations/infoblox/utils/client.py b/nautobot_ssot/integrations/infoblox/utils/client.py
index a46344f28..0863cb6db 100644
--- a/nautobot_ssot/integrations/infoblox/utils/client.py
+++ b/nautobot_ssot/integrations/infoblox/utils/client.py
@@ -791,7 +791,8 @@ def get_all_subnets(self, prefix: str = None):
"_return_fields": "network,network_view,comment,extattrs,rir_organization,rir,vlans",
"_max_results": 10000,
}
-
+ if PLUGIN_CFG.get("NAUTOBOT_INFOBLOX_NETWORK_VIEW"):
+ params.update({"network_view": PLUGIN_CFG["NAUTOBOT_INFOBLOX_NETWORK_VIEW"]})
if prefix:
params.update({"network": prefix})
try:
@@ -1172,10 +1173,19 @@ def create_vlan(self, vlan_id, vlan_name, vlan_view):
@staticmethod
def get_ipaddr_status(ip_record: dict) -> str:
- """Determine the IPAddress status based upon types and usage keys."""
+ """Determine the IPAddress status based on the status key."""
+ if "USED" in ip_record["status"]:
+ return "Active"
+ return "Reserved"
+
+ @staticmethod
+ def get_ipaddr_type(ip_record: dict) -> str:
+ """Determine the IPAddress type based on the usage key."""
if "DHCP" in ip_record["usage"]:
- return "DHCP"
- return "Active"
+ return "dhcp"
+ if "SLAAC" in ip_record["usage"]:
+ return "slaac"
+ return "host"
def _find_resource(self, resource, **params):
"""Find the resource for given parameters.
@@ -1275,6 +1285,8 @@ def get_network_containers(self, prefix: str = ""):
"_return_fields": "network,comment,network_view,extattrs,rir_organization,rir",
"_max_results": 100000,
}
+ if PLUGIN_CFG.get("NAUTOBOT_INFOBLOX_NETWORK_VIEW"):
+ params.update({"network_view": PLUGIN_CFG["NAUTOBOT_INFOBLOX_NETWORK_VIEW"]})
if prefix:
params.update({"network": prefix})
response = self._request("GET", url_path, params=params)
@@ -1317,6 +1329,8 @@ def get_child_network_containers(self, prefix: str):
"_return_fields": "network,comment,network_view,extattrs,rir_organization,rir",
"_max_results": 100000,
}
+ if PLUGIN_CFG.get("NAUTOBOT_INFOBLOX_NETWORK_VIEW"):
+ params.update({"network_view": PLUGIN_CFG["NAUTOBOT_INFOBLOX_NETWORK_VIEW"]})
params.update({"network_container": prefix})
response = self._request("GET", url_path, params=params)
response = response.json()
@@ -1363,7 +1377,8 @@ def get_child_subnets_from_container(self, prefix: str):
"_return_fields": "network,network_view,comment,extattrs,rir_organization,rir,vlans",
"_max_results": 10000,
}
-
+ if PLUGIN_CFG.get("NAUTOBOT_INFOBLOX_NETWORK_VIEW"):
+ params.update({"network_view": PLUGIN_CFG["NAUTOBOT_INFOBLOX_NETWORK_VIEW"]})
params.update({"network_container": prefix})
try:
diff --git a/nautobot_ssot/integrations/infoblox/utils/diffsync.py b/nautobot_ssot/integrations/infoblox/utils/diffsync.py
index ccbe3f12c..88eecbc4c 100644
--- a/nautobot_ssot/integrations/infoblox/utils/diffsync.py
+++ b/nautobot_ssot/integrations/infoblox/utils/diffsync.py
@@ -58,7 +58,7 @@ def get_ext_attr_dict(extattrs: dict):
"""
fixed_dict = {}
for key, value in extattrs.items():
- fixed_dict[slugify(key)] = value["value"]
+ fixed_dict[slugify(key).replace("-", "_")] = value["value"]
return fixed_dict
diff --git a/nautobot_ssot/integrations/ipfabric/constants.py b/nautobot_ssot/integrations/ipfabric/constants.py
index a9ccceb2d..e490fd21f 100644
--- a/nautobot_ssot/integrations/ipfabric/constants.py
+++ b/nautobot_ssot/integrations/ipfabric/constants.py
@@ -23,3 +23,4 @@
SAFE_DELETE_VLAN_STATUS = CONFIG.get("ipfabric_safe_delete_vlan_status", "Deprecated")
SAFE_DELETE_IPADDRESS_STATUS = CONFIG.get("ipfabric_safe_delete_ipaddress_status", "Deprecated")
LAST_SYNCHRONIZED_CF_NAME = "last_synced_from_sor"
+IP_FABRIC_USE_CANONICAL_INTERFACE_NAME = CONFIG.get("ipfabric_use_canonical_interface_name", False)
diff --git a/nautobot_ssot/integrations/ipfabric/diffsync/adapter_ipfabric.py b/nautobot_ssot/integrations/ipfabric/diffsync/adapter_ipfabric.py
index d3e761e9b..0a2dbc9bc 100644
--- a/nautobot_ssot/integrations/ipfabric/diffsync/adapter_ipfabric.py
+++ b/nautobot_ssot/integrations/ipfabric/diffsync/adapter_ipfabric.py
@@ -7,15 +7,18 @@
from nautobot.dcim.models import Device
from nautobot.ipam.models import VLAN
from netutils.mac import mac_to_format
+from netutils.interface import canonical_interface_name
from nautobot_ssot.integrations.ipfabric.constants import (
- DEFAULT_INTERFACE_TYPE,
DEFAULT_INTERFACE_MTU,
DEFAULT_INTERFACE_MAC,
DEFAULT_DEVICE_ROLE,
DEFAULT_DEVICE_STATUS,
+ IP_FABRIC_USE_CANONICAL_INTERFACE_NAME,
)
from nautobot_ssot.integrations.ipfabric.diffsync import DiffSyncModelAdapters
+from nautobot_ssot.integrations.ipfabric.utilities import utils as ipfabric_utils
+
logger = logging.getLogger("nautobot.jobs")
@@ -54,10 +57,13 @@ def load_device_interfaces(self, device_model, interfaces, device_primary_ip):
for iface in device_interfaces:
ip_address = iface.get("primaryIp")
+ iface_name = iface["intName"]
+ if IP_FABRIC_USE_CANONICAL_INTERFACE_NAME:
+ iface_name = canonical_interface_name(iface_name)
try:
interface = self.interface(
diffsync=self,
- name=iface.get("intName"),
+ name=iface_name,
device_name=iface.get("hostname"),
description=iface.get("dscr", ""),
enabled=True,
@@ -65,7 +71,7 @@ def load_device_interfaces(self, device_model, interfaces, device_primary_ip):
if iface.get("mac")
else DEFAULT_INTERFACE_MAC,
mtu=iface.get("mtu") if iface.get("mtu") else DEFAULT_INTERFACE_MTU,
- type=DEFAULT_INTERFACE_TYPE,
+ type=ipfabric_utils.convert_media_type(iface.get("media") or ""),
mgmt_only=iface.get("mgmt_only", False),
ip_address=ip_address,
# TODO: why is only IPv4? and why /32?
diff --git a/nautobot_ssot/integrations/ipfabric/utilities/utils.py b/nautobot_ssot/integrations/ipfabric/utilities/utils.py
new file mode 100644
index 000000000..fad946197
--- /dev/null
+++ b/nautobot_ssot/integrations/ipfabric/utilities/utils.py
@@ -0,0 +1,135 @@
+"""General utils for IPFabric."""
+from nautobot_ssot.integrations.ipfabric.constants import DEFAULT_INTERFACE_TYPE
+
+
+VIRTUAL = "virtual"
+BRIDGE = "bridge"
+LAG = "lag"
+
+GBIC = "gbic"
+SFP = "sfp"
+LR = "lr"
+SR = "sr"
+SX = "sx"
+LX = "lx"
+XFP = "xfp"
+X2 = "x2"
+XENPAK = "xenpak"
+QSFP = "qsfp"
+
+HUNDRED_MEG = "100m"
+HUNDRED_MEG_BASE = "100base"
+GIG = "1g"
+GIG_BASE = "1000base"
+RJ45 = "rj45"
+TWO_AND_HALF_GIG_BASE = "2.5gbase"
+FIVE_GIG_BASE = "5gbase"
+TEN_GIG = "10g"
+TEN_GIG_BASE_T = "10gbaset"
+TWENTY_FIVE_GIG = "25g"
+FORTY_GIG = "40g"
+FIFTY_GIG = "50g"
+HUNDRED_GIG = "100g"
+TWO_HUNDRED_GIG = "200g"
+FOUR_HUNDRED_GIG = "400g"
+EIGHT_HUNDRED_GIG = "800g"
+
+
+def convert_media_type( # pylint: disable=too-many-return-statements,too-many-branches,too-many-statements
+ media_type: str,
+) -> str:
+ """Convert provided `media_type` to value used by Nautobot.
+
+ Args:
+ media_type: The media type of an inteface (i.e. SFP-10GBase)
+
+ Returns:
+ str: The corresponding represention of `media_type` in Nautobot.
+ """
+ media_type = media_type.lower().replace("-", "")
+ if VIRTUAL in media_type:
+ return VIRTUAL
+ if BRIDGE in media_type:
+ return BRIDGE
+ if LAG in media_type:
+ return LAG
+
+ if TEN_GIG_BASE_T in media_type:
+ return "10gbase-t"
+
+ # Going from 10Gig to lower bandwidths to allow media that supports multiple
+ # bandwidths to use highest supported bandwidth
+ if TEN_GIG in media_type:
+ nautobot_media_type = "10gbase-x-"
+ if XFP in media_type:
+ nautobot_media_type += "xfp"
+ elif X2 in media_type:
+ nautobot_media_type += "x2"
+ elif XENPAK in media_type:
+ nautobot_media_type += "xenpak"
+ else:
+ nautobot_media_type += "sfpp"
+ return nautobot_media_type
+
+ # Flipping order of 5gig and 2.5g as both use the string 5gbase
+ if TWO_AND_HALF_GIG_BASE in media_type:
+ return "2.5gbase-t"
+
+ if FIVE_GIG_BASE in media_type:
+ return "5gbase-t"
+
+ if GIG_BASE in media_type or RJ45 in media_type or GIG in media_type:
+ nautobot_media_type = "1000base-"
+ if GBIC in media_type:
+ nautobot_media_type += "x-gbic"
+ elif SFP in media_type or SR in media_type or LR in media_type or SX in media_type or LX in media_type:
+ nautobot_media_type += "x-sfp"
+ else:
+ nautobot_media_type += "t"
+ return nautobot_media_type
+
+ if HUNDRED_MEG_BASE in media_type or HUNDRED_MEG in media_type:
+ return "100base-tx"
+
+ if TWENTY_FIVE_GIG in media_type:
+ return "25gbase-x-sfp28"
+
+ if FORTY_GIG in media_type:
+ return "40gbase-x-qsfpp"
+
+ if FIFTY_GIG in media_type:
+ return "50gbase-x-sfp56"
+
+ if HUNDRED_GIG in media_type:
+ nautobot_media_type = "100gbase-x-"
+ if QSFP in media_type:
+ nautobot_media_type += "qsfp28"
+ else:
+ nautobot_media_type += "cfp"
+ return nautobot_media_type
+
+ if TWO_HUNDRED_GIG in media_type:
+ nautobot_media_type = "200gbase-x-"
+ if QSFP in media_type:
+ nautobot_media_type += "qsfp56"
+ else:
+ nautobot_media_type += "cfp2"
+ return nautobot_media_type
+
+ if FOUR_HUNDRED_GIG in media_type:
+ nautobot_media_type = "400gbase-x-"
+ if QSFP in media_type:
+ nautobot_media_type += "qsfp112"
+ else:
+ nautobot_media_type += "osfp"
+ return nautobot_media_type
+
+ if EIGHT_HUNDRED_GIG in media_type:
+ nautobot_media_type = "800gbase-x-"
+ if QSFP in media_type:
+ nautobot_media_type += "qsfpdd"
+ else:
+ nautobot_media_type += "osfp"
+ return nautobot_media_type
+
+ return DEFAULT_INTERFACE_TYPE
diff --git a/nautobot_ssot/integrations/ipfabric/workers.py b/nautobot_ssot/integrations/ipfabric/workers.py
index 56eb66acf..c1281db8d 100644
--- a/nautobot_ssot/integrations/ipfabric/workers.py
+++ b/nautobot_ssot/integrations/ipfabric/workers.py
@@ -51,7 +51,7 @@ def ipfabric_logo(dispatcher):
def ipfabric(subcommand, **kwargs):
- """Interact with ipfabric plugin."""
+ """Interact with ipfabric app."""
return handle_subcommands("ipfabric", subcommand, **kwargs)
diff --git a/nautobot_ssot/integrations/servicenow/diffsync/adapter_nautobot.py b/nautobot_ssot/integrations/servicenow/diffsync/adapter_nautobot.py
index 440ec0f9c..77f6f5c0d 100644
--- a/nautobot_ssot/integrations/servicenow/diffsync/adapter_nautobot.py
+++ b/nautobot_ssot/integrations/servicenow/diffsync/adapter_nautobot.py
@@ -127,7 +127,7 @@ def load(self):
def tag_involved_objects(self, target):
"""Tag all objects that were successfully synced to the target."""
- # The SSoT Synced to ServiceNow Tag *should* have been created automatically during plugin installation
+ # The SSoT Synced to ServiceNow Tag *should* have been created automatically during app installation
# (see nautobot_ssot/integrations/servicenow/signals.py) but maybe a user deleted it inadvertently, so be safe:
tag, _ = Tag.objects.get_or_create(
name="SSoT Synced to ServiceNow",
diff --git a/nautobot_ssot/integrations/servicenow/forms.py b/nautobot_ssot/integrations/servicenow/forms.py
index d413faeb4..2ffbd99cf 100644
--- a/nautobot_ssot/integrations/servicenow/forms.py
+++ b/nautobot_ssot/integrations/servicenow/forms.py
@@ -8,7 +8,7 @@
class SSOTServiceNowConfigForm(forms.ModelForm):
- """Plugin configuration form for nautobot-ssot-servicenow."""
+ """App configuration form for nautobot-ssot-servicenow."""
servicenow_instance = forms.CharField(
required=True,
diff --git a/nautobot_ssot/integrations/servicenow/models.py b/nautobot_ssot/integrations/servicenow/models.py
index 623b0e86a..152761f66 100644
--- a/nautobot_ssot/integrations/servicenow/models.py
+++ b/nautobot_ssot/integrations/servicenow/models.py
@@ -7,7 +7,7 @@
class SSOTServiceNowConfig(BaseModel): # pylint: disable=nb-string-field-blank-null
- """Singleton data model describing the configuration of this plugin."""
+ """Singleton data model describing the configuration of this app."""
def delete(self, *args, **kwargs):
"""Cannot be deleted."""
diff --git a/nautobot_ssot/integrations/servicenow/views.py b/nautobot_ssot/integrations/servicenow/views.py
index 776c5ed5a..d155c1c4d 100644
--- a/nautobot_ssot/integrations/servicenow/views.py
+++ b/nautobot_ssot/integrations/servicenow/views.py
@@ -9,7 +9,7 @@
class SSOTServiceNowConfigView(UpdateView):
- """Plugin-level configuration view for nautobot-ssot-servicenow."""
+ """App configuration view for nautobot-ssot-servicenow."""
form_class = SSOTServiceNowConfigForm
template_name = "nautobot_ssot_servicenow/config.html"
diff --git a/nautobot_ssot/jobs/__init__.py b/nautobot_ssot/jobs/__init__.py
index b1bab597f..fce386eae 100644
--- a/nautobot_ssot/jobs/__init__.py
+++ b/nautobot_ssot/jobs/__init__.py
@@ -1,4 +1,4 @@
-"""Plugin provision of Nautobot Job subclasses."""
+"""App provision of Nautobot Job subclasses."""
from django.conf import settings
diff --git a/nautobot_ssot/jobs/examples.py b/nautobot_ssot/jobs/examples.py
index 33225702e..69c88998d 100644
--- a/nautobot_ssot/jobs/examples.py
+++ b/nautobot_ssot/jobs/examples.py
@@ -379,7 +379,7 @@ class NautobotRemote(DiffSync):
"""DiffSync adapter class for loading data from a remote Nautobot instance using Python requests.
In a more realistic example, you'd probably use PyNautobot here instead of raw requests,
- but we didn't want to add PyNautobot as a dependency of this plugin just to make an example more realistic.
+ but we didn't want to add PyNautobot as a dependency of this app just to make an example more realistic.
"""
# Model classes used by this adapter class
diff --git a/nautobot_ssot/management/__init__.py b/nautobot_ssot/management/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/nautobot_ssot/management/commands/__init__.py b/nautobot_ssot/management/commands/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/nautobot_ssot/management/commands/elongate_interface_names.py b/nautobot_ssot/management/commands/elongate_interface_names.py
new file mode 100644
index 000000000..f562b765d
--- /dev/null
+++ b/nautobot_ssot/management/commands/elongate_interface_names.py
@@ -0,0 +1,67 @@
+"""Django Management command to update DCIM.Interface names."""
+from django.core.management.base import BaseCommand
+
+from nautobot.dcim.models import Device
+
+from netutils.interface import canonical_interface_name
+
+
+class Command(BaseCommand):
+ """MGMT command to use netutils to update Nautobot Interface names to use long format."""
+
+ help = (
+ "Elongate DCIM.Interface names in Nautobot based off of netutils: "
+ "https://netutils.readthedocs.io/en/latest/dev/code_reference/interface/#netutils.interface.canonical_interface_name"
+ )
+
+ def add_arguments(self, parser): # noqa: D102
+ parser.add_argument(
+ "-d",
+ "--devices",
+ default=None,
+ help="DCIM.Device names to limit which devices have their interface names updated (comma separated).",
+ )
+
+ parser.add_argument(
+ "-l",
+ "--locations",
+ default=None,
+ help="DCIM.Location names to limit which devices have their interface names updated (comma separated).",
+ )
+
+ parser.add_argument(
+ "--cf_systems_of_record",
+ default=None,
+ help="Limits DCIM.Devices in scope to those that have a custom_field.system_of_record value to what is passed.",
+ )
+
+ def handle(self, *args, **options): # noqa: D102
+ device_limit = options.get("devices")
+ location_limit = options.get("locations")
+ if device_limit and location_limit:
+ raise ValueError('Only one of "--devices" and "--locations" may be used.')
+
+ if device_limit:
+ device_option = [device.strip() for device in device_limit.split(",")]
+ devices = Device.objects.filter(name__in=device_option)
+ elif location_limit:
+ device_option = [location.strip() for location in location_limit.split(",")]
+ devices = Device.objects.filter(location__name__in=device_option)
+ else:
+ devices = Device.objects.all()
+
+ cf_systems_of_record_limit = options.get("cf_systems_of_record")
+ if cf_systems_of_record_limit:
+ sor_option = [sor.strip() for sor in cf_systems_of_record_limit.split(",")]
+ devices = devices.filter(_custom_field_data__system_of_record__in=sor_option)
+
+ for device in devices:
+ for interface in device.interfaces.all():
+ new_name = canonical_interface_name(interface.name)
+ if interface.name != new_name:
+ self.stdout.write(
+ self.style.WARNING(f"Updating {device.name}.{interface.name} >> ")
+ + self.style.SUCCESS(f"{new_name}")
+ )
+ interface.name = canonical_interface_name(new_name)
+ interface.validated_save()
diff --git a/nautobot_ssot/models.py b/nautobot_ssot/models.py
index 6f7a7e2a6..48a54255d 100644
--- a/nautobot_ssot/models.py
+++ b/nautobot_ssot/models.py
@@ -6,7 +6,7 @@
- A JobResult is created each time a data sync is requested.
- This stores a reference to the specific sync operation requested (JobResult.name),
much as a Job-related JobResult would reference the name of the Job.
- - This stores a 'job_id', which this plugin uses to reference the specific sync instance.
+ - This stores a 'job_id', which this app uses to reference the specific sync instance.
- This stores the 'created' and 'completed' timestamps, and the requesting user (if any)
- This stores the overall 'status' of the job (pending, running, completed, failed, errored.)
- This stores a 'data' field which, in theory can store arbitrary JSON data, but in practice
@@ -141,7 +141,7 @@ class SyncLogEntry(BaseModel): # pylint: disable=nb-string-field-blank-null
Detailed sync logs are recorded in this model, rather than in JobResult.data, because
JobResult.data imposes fairly strict expectations about the structure of its contents
- that do not align well with the requirements of this plugin. Also, storing log entries as individual
+ that do not align well with the requirements of this app. Also, storing log entries as individual
database records rather than a single JSON blob allows us to filter, query, sort, etc. as desired.
This model somewhat "shadows" Nautobot's built-in ObjectChange model; the key distinction to
diff --git a/nautobot_ssot/navigation.py b/nautobot_ssot/navigation.py
index b06a6d4c8..398e493f8 100644
--- a/nautobot_ssot/navigation.py
+++ b/nautobot_ssot/navigation.py
@@ -1,4 +1,4 @@
-"""Plugin additions to the Nautobot navigation menu."""
+"""App additions to the Nautobot navigation menu."""
from nautobot.apps.ui import NavMenuGroup, NavMenuItem, NavMenuTab
diff --git a/nautobot_ssot/template_content.py b/nautobot_ssot/template_content.py
index e53ac772f..caba66cb9 100644
--- a/nautobot_ssot/template_content.py
+++ b/nautobot_ssot/template_content.py
@@ -1,4 +1,4 @@
-"""Plugin template content extensions of base Nautobot views."""
+"""App template content extensions of base Nautobot views."""
from django.urls import reverse
diff --git a/nautobot_ssot/tests/__init__.py b/nautobot_ssot/tests/__init__.py
index 547039b14..9d5ce55ed 100644
--- a/nautobot_ssot/tests/__init__.py
+++ b/nautobot_ssot/tests/__init__.py
@@ -1,4 +1,4 @@
-"""Unit tests for nautobot_ssot plugin."""
+"""Unit tests for nautobot_ssot app."""
from django.conf import settings
diff --git a/nautobot_ssot/tests/aci/__init__.py b/nautobot_ssot/tests/aci/__init__.py
index 4b5b27dac..0669bd022 100644
--- a/nautobot_ssot/tests/aci/__init__.py
+++ b/nautobot_ssot/tests/aci/__init__.py
@@ -1 +1 @@
-"""Unit tests for nautobot_plugin_chatops_aci plugin."""
+"""Unit tests for nautobot_app_chatops_aci app."""
diff --git a/nautobot_ssot/tests/aristacv/__init__.py b/nautobot_ssot/tests/aristacv/__init__.py
index ba18399ce..411b25b20 100644
--- a/nautobot_ssot/tests/aristacv/__init__.py
+++ b/nautobot_ssot/tests/aristacv/__init__.py
@@ -1 +1 @@
-"""Unit tests for aristacv_sync plugin."""
+"""Unit tests for aristacv_sync app."""
diff --git a/nautobot_ssot/tests/aristacv/test_utils_nautobot.py b/nautobot_ssot/tests/aristacv/test_utils_nautobot.py
index b47976e0a..ebf3c948c 100644
--- a/nautobot_ssot/tests/aristacv/test_utils_nautobot.py
+++ b/nautobot_ssot/tests/aristacv/test_utils_nautobot.py
@@ -66,7 +66,7 @@ def test_verify_import_tag_fail(self):
@skip("DLC App disabled")
def test_get_device_version_dlc_success(self):
- """Test the get_device_version method pulling from Device Lifecycle plugin."""
+ """Test the get_device_version method pulling from Device Lifecycle app."""
software_relation = Relationship.objects.get(label="Software on Device")
mock_version = MagicMock()
@@ -82,7 +82,7 @@ def test_get_device_version_dlc_success(self):
@skip("DLC App disabled")
def test_get_device_version_dlc_fail(self):
- """Test the get_device_version method pulling from Device Lifecycle plugin but failing."""
+ """Test the get_device_version method pulling from Device Lifecycle app but failing."""
mock_device = MagicMock()
mock_device.get_relationships = MagicMock()
mock_device.get_relationships.return_value = {}
diff --git a/nautobot_ssot/tests/ipfabric/__init__.py b/nautobot_ssot/tests/ipfabric/__init__.py
index 033383d79..5de00cf58 100644
--- a/nautobot_ssot/tests/ipfabric/__init__.py
+++ b/nautobot_ssot/tests/ipfabric/__init__.py
@@ -1 +1 @@
-"""Unit tests for nautobot_ssot.integrations.ipfabric plugin."""
+"""Unit tests for nautobot_ssot.integrations.ipfabric app."""
diff --git a/nautobot_ssot/tests/ipfabric/test_ipfabric_adapter.py b/nautobot_ssot/tests/ipfabric/test_ipfabric_adapter.py
index 18d2d5b28..7e9d57fcf 100644
--- a/nautobot_ssot/tests/ipfabric/test_ipfabric_adapter.py
+++ b/nautobot_ssot/tests/ipfabric/test_ipfabric_adapter.py
@@ -1,6 +1,6 @@
"""Unit tests for the IPFabric DiffSync adapter class."""
import json
-from unittest.mock import MagicMock
+from unittest.mock import MagicMock, patch
from django.test import TestCase
from nautobot.extras.models import JobResult
@@ -83,3 +83,28 @@ def test_data_loading(self):
self.assertTrue(hasattr(interface, "ip_address"))
self.assertTrue(hasattr(interface, "subnet_mask"))
self.assertTrue(hasattr(interface, "type"))
+
+ @patch("nautobot_ssot.integrations.ipfabric.diffsync.adapter_ipfabric.IP_FABRIC_USE_CANONICAL_INTERFACE_NAME", True)
+ def test_data_loading_elongate_interface_names(self):
+ """Test the load() function with using long form interface names."""
+
+ # Create a mock client
+ ipfabric_client = MagicMock()
+ ipfabric_client.inventory.sites.all.return_value = SITE_FIXTURE
+ ipfabric_client.inventory.devices.all.return_value = DEVICE_INVENTORY_FIXTURE
+ ipfabric_client.fetch_all = MagicMock(
+ side_effect=(lambda x: VLAN_FIXTURE if x == "tables/vlan/site-summary" else "")
+ )
+ ipfabric_client.inventory.interfaces.all.return_value = INTERFACE_FIXTURE
+
+ job = IpFabricDataSource()
+ job.job_result = JobResult.objects.create(name=job.class_path, task_name="fake task", worker="default")
+ ipfabric = IPFabricDiffSync(job=job, sync=None, client=ipfabric_client)
+ ipfabric.load()
+
+ # Validate long interface names were created by not raising an exception
+ # when performing `DiffSync.get()`
+ ipfabric.get("interface", {"name": "ipip", "device_name": "nyc-rtr-01"})
+ ipfabric.get("interface", {"name": "Ethernet15", "device_name": "nyc-leaf-01"})
+ ipfabric.get("interface", {"name": "GigabitEthernet4", "device_name": "jcy-rtr-02"})
+ ipfabric.get("interface", {"name": "Ethernet1", "device_name": "nyc-rtr-01"})
diff --git a/nautobot_ssot/tests/ipfabric/test_nautobot_adapter.py b/nautobot_ssot/tests/ipfabric/test_nautobot_adapter.py
index 15963bf79..310ba292f 100644
--- a/nautobot_ssot/tests/ipfabric/test_nautobot_adapter.py
+++ b/nautobot_ssot/tests/ipfabric/test_nautobot_adapter.py
@@ -27,10 +27,10 @@
# device_role = DeviceRole.objects.create(name="Router", slug="router")
# Device.objects.create(
-# name="csr1", device_type=device_type, device_role=device_role, site=site_1, status=status_active
+# name="csr1", device_type=device_type, role=device_role, site=site_1, status=status_active
# )
# Device.objects.create(
-# name="csr2", device_type=device_type, device_role=device_role, site=site_2, status=status_active
+# name="csr2", device_type=device_type, role=device_role, site=site_2, status=status_active
# )
# VLAN.objects.create(name="VLAN101", vid=101, status=status_active, site=site_1)
diff --git a/nautobot_ssot/tests/ipfabric/test_utils.py b/nautobot_ssot/tests/ipfabric/test_utils.py
new file mode 100644
index 000000000..1d34c3032
--- /dev/null
+++ b/nautobot_ssot/tests/ipfabric/test_utils.py
@@ -0,0 +1,103 @@
+"""Tests for IPFabric utilities.utils."""
+from django.test import SimpleTestCase
+
+from nautobot_ssot.integrations.ipfabric.constants import DEFAULT_INTERFACE_TYPE
+from nautobot_ssot.integrations.ipfabric.utilities import utils
+
+
+class TestUtils(SimpleTestCase): # pylint: disable=too-many-public-methods
+ """Test IPFabric utilities.utils."""
+
+ def test_virtual_interface(self):
+ self.assertEqual("virtual", utils.convert_media_type("Virtual"))
+
+ def test_bridge_interface(self):
+ self.assertEqual("bridge", utils.convert_media_type("Bridge"))
+
+ def test_lag_interface(self):
+ self.assertEqual("lag", utils.convert_media_type("LAG"))
+
+ def test_hunderd_meg_base_t_interface(self):
+ self.assertEqual("100base-tx", utils.convert_media_type("100Base-T"))
+
+ def test_hundred_meg_interface(self):
+ self.assertEqual("100base-tx", utils.convert_media_type("100MegabitEthernet"))
+
+ def test_gig_base_t_interface(self):
+ self.assertEqual("1000base-t", utils.convert_media_type("1000BaseT"))
+ self.assertEqual("1000base-t", utils.convert_media_type("10/100/1000BaseTX"))
+
+ def test_rj45_uses_gig_base_t_interface(self):
+ self.assertEqual("1000base-t", utils.convert_media_type("RJ45"))
+
+ def test_gig_default_uses_base_t_interface(self):
+ self.assertEqual("1000base-t", utils.convert_media_type("1GigThisUsesDefault"))
+
+ def test_gig_sfp_interface(self):
+ self.assertEqual("1000base-x-sfp", utils.convert_media_type("10/100/1000BaseTX SFP"))
+
+ def test_gig_sfp_used_for_sfp_type_interface(self):
+ self.assertEqual("1000base-x-sfp", utils.convert_media_type("1000BaseLX"))
+ self.assertEqual("1000base-x-sfp", utils.convert_media_type("1000BaseSX"))
+ self.assertEqual("1000base-x-sfp", utils.convert_media_type("1000BaseLR"))
+ self.assertEqual("1000base-x-sfp", utils.convert_media_type("1000BaseSR"))
+
+ def test_gig_gbic_interface(self):
+ self.assertEqual("1000base-x-gbic", utils.convert_media_type("10/100/1000BaseTX GBIC"))
+
+ def test_two_and_half_gig_base_t_interface(self):
+ self.assertEqual("2.5gbase-t", utils.convert_media_type("100/1000/2.5GBaseTX"))
+
+ def test_five_gig_base_t_interface(self):
+ self.assertEqual("5gbase-t", utils.convert_media_type("100/1000/2.5G/5GBaseTX"))
+
+ def test_ten_gig_xfp_interface(self):
+ self.assertEqual("10gbase-x-xfp", utils.convert_media_type("10GBase XFP"))
+
+ def test_ten_gig_x2_interface(self):
+ self.assertEqual("10gbase-x-x2", utils.convert_media_type("10GBase X2"))
+
+ def test_ten_gig_xenpak_interface(self):
+ self.assertEqual("10gbase-x-xenpak", utils.convert_media_type("10GBase XENPAK"))
+
+ def test_ten_gig_sfp_interface(self):
+ self.assertEqual("10gbase-x-sfpp", utils.convert_media_type("10GBase SFP"))
+
+ def test_ten_gig_default_uses_sfp_interface(self):
+ self.assertEqual("10gbase-x-sfpp", utils.convert_media_type("10G"))
+
+ def test_twenty_five_gig_sfp_interface(self):
+ self.assertEqual("25gbase-x-sfp28", utils.convert_media_type("25G"))
+
+ def test_forty_gig_sfp_interface(self):
+ self.assertEqual("40gbase-x-qsfpp", utils.convert_media_type("40G"))
+
+ def test_fifty_gig_sfp_interface(self):
+ self.assertEqual("50gbase-x-sfp56", utils.convert_media_type("50G"))
+
+ def test_hundred_gig_qsfp_interface(self):
+ self.assertEqual("100gbase-x-qsfp28", utils.convert_media_type("100G QSFP"))
+
+ def test_hundred_gig_default_uses_cfp_interface(self):
+ self.assertEqual("100gbase-x-cfp", utils.convert_media_type("100G"))
+
+ def test_two_hundred_gig_qsfp_interface(self):
+ self.assertEqual("200gbase-x-qsfp56", utils.convert_media_type("200G QSFP"))
+
+ def test_two_hundred_gig_default_uses_cfp_interface(self):
+ self.assertEqual("200gbase-x-cfp2", utils.convert_media_type("200G"))
+
+ def test_four_hundred_gig_qsfp_interface(self):
+ self.assertEqual("400gbase-x-qsfp112", utils.convert_media_type("400G QSFP"))
+
+ def test_four_hundred_gig_default_uses_osfp_interface(self):
+ self.assertEqual("400gbase-x-osfp", utils.convert_media_type("400G"))
+
+ def test_eight_hundred_gig_qsfp_interface(self):
+ self.assertEqual("800gbase-x-qsfpdd", utils.convert_media_type("800G QSFP"))
+
+ def test_eight_hundred_gig_default_uses_osfp_interface(self):
+ self.assertEqual("800gbase-x-osfp", utils.convert_media_type("800G"))
+
+ def test_unknown_interface_uses_default_interface(self):
+ self.assertEqual(DEFAULT_INTERFACE_TYPE, utils.convert_media_type("ThisShouldGiveTheDefault"))
diff --git a/nautobot_ssot/tests/servicenow/test_jobs.py b/nautobot_ssot/tests/servicenow/test_jobs.py
index e054bb8cc..141686929 100644
--- a/nautobot_ssot/tests/servicenow/test_jobs.py
+++ b/nautobot_ssot/tests/servicenow/test_jobs.py
@@ -1,4 +1,4 @@
-"""Test the Job class in this plugin."""
+"""Test the Job class in this app."""
import os
from unittest import mock
diff --git a/nautobot_ssot/tests/test_api.py b/nautobot_ssot/tests/test_api.py
index 73572c41e..a03f5b0c8 100644
--- a/nautobot_ssot/tests/test_api.py
+++ b/nautobot_ssot/tests/test_api.py
@@ -11,7 +11,7 @@
class PlaceholderAPITest(TestCase):
- """Test the NautobotSSOTPlugin API."""
+ """Test the NautobotSSOTApp API."""
def setUp(self):
"""Create a superuser and token for API calls."""
diff --git a/nautobot_ssot/tests/test_contrib.py b/nautobot_ssot/tests/test_contrib.py
index 7623d74a2..537f4f66a 100644
--- a/nautobot_ssot/tests/test_contrib.py
+++ b/nautobot_ssot/tests/test_contrib.py
@@ -390,7 +390,7 @@ class BaseModelCustomFieldTest(TestCase):
def test_custom_field_set(self):
"""Test whether setting a custom field value works."""
custom_field_name = "Is Global"
- custom_field = CustomField.objects.create(key=custom_field_name, label=custom_field_name, type="boolean")
+ custom_field = CustomField.objects.create(key="is_global", label=custom_field_name, type="boolean")
custom_field.content_types.set([ContentType.objects.get_for_model(Provider)])
class ProviderModel(NautobotModel):
@@ -402,7 +402,7 @@ class ProviderModel(NautobotModel):
name: str
- is_global: Annotated[bool, CustomFieldAnnotation(name=custom_field_name)] = False
+ is_global: Annotated[bool, CustomFieldAnnotation(name="is_global")] = False
provider_name = "Test Provider"
provider = Provider.objects.create(name=provider_name)
@@ -413,7 +413,7 @@ class ProviderModel(NautobotModel):
provider.refresh_from_db()
self.assertEqual(
- provider.cf[custom_field_name],
+ provider.cf["is_global"],
updated_custom_field_value,
"Setting a custom field through 'NautobotModel' does not work.",
)
diff --git a/nautobot_ssot/tests/test_management.py b/nautobot_ssot/tests/test_management.py
new file mode 100644
index 000000000..9a91acb70
--- /dev/null
+++ b/nautobot_ssot/tests/test_management.py
@@ -0,0 +1,189 @@
+"""Test cases for custom Django MGMT commands."""
+from io import StringIO
+
+from django.contrib.contenttypes.models import ContentType
+from django.core.management import call_command
+from django.test import TestCase
+
+from nautobot.dcim.models import (
+ Device,
+ DeviceType,
+ Location,
+ LocationType,
+ Manufacturer,
+ Platform,
+)
+from nautobot.extras.models import CustomField, Role, Status
+
+
+class TestElongateInterfaceNames(TestCase):
+ """Unittests for elongate_interface_names command."""
+
+ def setUp(self): # pylint: disable=too-many-locals
+ """Per-test setup."""
+ ct_device = ContentType.objects.get_for_model(Device)
+ active_status = Status.objects.get(name="Active")
+
+ cf_sor = CustomField.objects.create(label="system_of_record", type="text")
+ cf_sor.content_types.add(ct_device)
+
+ role = Role.objects.create(name="ssot_test")
+ role.content_types.add(ct_device)
+
+ location_type = LocationType.objects.create(name="ssot_test")
+ location_type.content_types.add(ct_device)
+ location_1 = Location.objects.create(name="ssot_test_1", status=active_status, location_type=location_type)
+ location_2 = Location.objects.create(name="ssot_test_2", status=active_status, location_type=location_type)
+ location_3 = Location.objects.create(name="ssot_test_3", status=active_status, location_type=location_type)
+
+ manufacturer = Manufacturer.objects.create(name="ssot_test")
+ platform = Platform.objects.create(name="ssot_test", manufacturer=manufacturer)
+ device_type = DeviceType.objects.create(model="ssot_test", manufacturer=manufacturer)
+ device1 = Device.objects.create(
+ name="ssot_test_1",
+ status=active_status,
+ location=location_1,
+ platform=platform,
+ role=role,
+ device_type=device_type,
+ _custom_field_data={"system_of_record": "ipfabric"},
+ )
+ device2 = Device.objects.create(
+ name="ssot_test_2",
+ status=active_status,
+ location=location_1,
+ platform=platform,
+ role=role,
+ device_type=device_type,
+ _custom_field_data={"system_of_record": "servicenow"},
+ )
+ device3 = Device.objects.create(
+ name="ssot_test_3",
+ status=active_status,
+ location=location_2,
+ platform=platform,
+ role=role,
+ device_type=device_type,
+ )
+ device4 = Device.objects.create(
+ name="ssot_test_4",
+ status=active_status,
+ location=location_3,
+ platform=platform,
+ role=role,
+ device_type=device_type,
+ )
+
+ device1.interfaces.create(name="GigabitEthernet1", status=active_status, type="1000base-t")
+ device1.interfaces.create(name="ge2", status=active_status, type="1000base-t")
+ device1.interfaces.create(name="skipme", status=active_status, type="1000base-t")
+
+ device2.interfaces.create(name="ge2", status=active_status, type="1000base-t")
+ device3.interfaces.create(name="ge2", status=active_status, type="1000base-t")
+ device4.interfaces.create(name="ge1", status=active_status, type="1000base-t")
+ device4.interfaces.create(name="ge2", status=active_status, type="1000base-t")
+ return super().setUp()
+
+ def test_pass_arg_devices_single(self):
+ out = StringIO()
+ call_command("elongate_interface_names", "--no-color", "--skip-checks", devices="ssot_test_1", stdout=out)
+ self.assertEqual(out.getvalue().strip(), "Updating ssot_test_1.ge2 >> GigabitEthernet2")
+
+ def test_pass_arg_devices_single_multiple_interfaces(self):
+ out = StringIO()
+ call_command("elongate_interface_names", "--no-color", "--skip-checks", devices="ssot_test_4", stdout=out)
+ self.assertEqual(
+ out.getvalue().strip(),
+ "Updating ssot_test_4.ge1 >> GigabitEthernet1\nUpdating ssot_test_4.ge2 >> GigabitEthernet2",
+ )
+
+ def test_pass_arg_devices_multiple(self):
+ out = StringIO()
+ call_command(
+ "elongate_interface_names", "--no-color", "--skip-checks", devices="ssot_test_1, ssot_test_2", stdout=out
+ )
+ self.assertEqual(
+ out.getvalue().strip(),
+ "Updating ssot_test_1.ge2 >> GigabitEthernet2\nUpdating ssot_test_2.ge2 >> GigabitEthernet2",
+ )
+
+ def test_pass_arg_locations_single(self):
+ out = StringIO()
+ call_command("elongate_interface_names", "--no-color", "--skip-checks", locations="ssot_test_1", stdout=out)
+ self.assertEqual(
+ out.getvalue().strip(),
+ "Updating ssot_test_1.ge2 >> GigabitEthernet2\nUpdating ssot_test_2.ge2 >> GigabitEthernet2",
+ )
+
+ def test_pass_arg_locations_multiple(self):
+ out = StringIO()
+ call_command(
+ "elongate_interface_names", "--no-color", "--skip-checks", locations="ssot_test_1, ssot_test_2", stdout=out
+ )
+ self.assertEqual(
+ out.getvalue().strip(),
+ (
+ "Updating ssot_test_1.ge2 >> GigabitEthernet2\n"
+ "Updating ssot_test_2.ge2 >> GigabitEthernet2\n"
+ "Updating ssot_test_3.ge2 >> GigabitEthernet2"
+ ),
+ )
+
+ def test_pass_no_args(self):
+ out = StringIO()
+ call_command("elongate_interface_names", "--no-color", "--skip-checks", stdout=out)
+ self.assertEqual(
+ out.getvalue().strip(),
+ (
+ "Updating ssot_test_1.ge2 >> GigabitEthernet2\n"
+ "Updating ssot_test_2.ge2 >> GigabitEthernet2\n"
+ "Updating ssot_test_3.ge2 >> GigabitEthernet2\n"
+ "Updating ssot_test_4.ge1 >> GigabitEthernet1\n"
+ "Updating ssot_test_4.ge2 >> GigabitEthernet2"
+ ),
+ )
+
+ def test_pass_devices_and_locations_arg(self):
+ out = StringIO()
+ with self.assertRaises(ValueError, msg='Only one of "--devices" and "--locations" may be used.'):
+ call_command(
+ "elongate_interface_names",
+ "--no-color",
+ "--skip-checks",
+ devices="ssot_test_1",
+ locations="ssot_test_1",
+ stdout=out,
+ )
+
+ def test_pass_cf_sor_arg_single(self):
+ out = StringIO()
+ call_command(
+ "elongate_interface_names", "--no-color", "--skip-checks", cf_systems_of_record="ipfabric", stdout=out
+ )
+ self.assertEqual(out.getvalue().strip(), "Updating ssot_test_1.ge2 >> GigabitEthernet2")
+
+ def test_pass_cf_sor_arg_multiple(self):
+ out = StringIO()
+ call_command(
+ "elongate_interface_names",
+ "--no-color",
+ "--skip-checks",
+ cf_systems_of_record="ipfabric,servicenow",
+ stdout=out,
+ )
+ self.assertEqual(
+ out.getvalue().strip(),
+ "Updating ssot_test_1.ge2 >> GigabitEthernet2\nUpdating ssot_test_2.ge2 >> GigabitEthernet2",
+ )
+
+ def test_pass_location_and_cf_sor_args(self):
+ out = StringIO()
+ call_command(
+ "elongate_interface_names",
+ "--no-color",
+ "--skip-checks",
+ locations="ssot_test_1,ssot_test_3",
+ cf_systems_of_record="ipfabric",
+ stdout=out,
+ )
+ self.assertEqual(out.getvalue().strip(), "Updating ssot_test_1.ge2 >> GigabitEthernet2")
diff --git a/nautobot_ssot/urls.py b/nautobot_ssot/urls.py
index 5e3b0dc93..f7f9025ce 100644
--- a/nautobot_ssot/urls.py
+++ b/nautobot_ssot/urls.py
@@ -1,4 +1,4 @@
-"""Django urlpatterns declaration for nautobot_ssot plugin."""
+"""Django urlpatterns declaration for nautobot_ssot app."""
from django.urls import path
diff --git a/poetry.lock b/poetry.lock
index 761d3c814..286387fde 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -1,4 +1,4 @@
-# This file is automatically @generated by Poetry 1.6.1 and should not be changed by hand.
+# This file is automatically @generated by Poetry 1.7.1 and should not be changed by hand.
[[package]]
name = "aiodns"
@@ -885,34 +885,34 @@ dev = ["polib"]
[[package]]
name = "cryptography"
-version = "41.0.4"
+version = "41.0.6"
description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers."
optional = false
python-versions = ">=3.7"
files = [
- {file = "cryptography-41.0.4-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:80907d3faa55dc5434a16579952ac6da800935cd98d14dbd62f6f042c7f5e839"},
- {file = "cryptography-41.0.4-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:35c00f637cd0b9d5b6c6bd11b6c3359194a8eba9c46d4e875a3660e3b400005f"},
- {file = "cryptography-41.0.4-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cecfefa17042941f94ab54f769c8ce0fe14beff2694e9ac684176a2535bf9714"},
- {file = "cryptography-41.0.4-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e40211b4923ba5a6dc9769eab704bdb3fbb58d56c5b336d30996c24fcf12aadb"},
- {file = "cryptography-41.0.4-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:23a25c09dfd0d9f28da2352503b23e086f8e78096b9fd585d1d14eca01613e13"},
- {file = "cryptography-41.0.4-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:2ed09183922d66c4ec5fdaa59b4d14e105c084dd0febd27452de8f6f74704143"},
- {file = "cryptography-41.0.4-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:5a0f09cefded00e648a127048119f77bc2b2ec61e736660b5789e638f43cc397"},
- {file = "cryptography-41.0.4-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:9eeb77214afae972a00dee47382d2591abe77bdae166bda672fb1e24702a3860"},
- {file = "cryptography-41.0.4-cp37-abi3-win32.whl", hash = "sha256:3b224890962a2d7b57cf5eeb16ccaafba6083f7b811829f00476309bce2fe0fd"},
- {file = "cryptography-41.0.4-cp37-abi3-win_amd64.whl", hash = "sha256:c880eba5175f4307129784eca96f4e70b88e57aa3f680aeba3bab0e980b0f37d"},
- {file = "cryptography-41.0.4-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:004b6ccc95943f6a9ad3142cfabcc769d7ee38a3f60fb0dddbfb431f818c3a67"},
- {file = "cryptography-41.0.4-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:86defa8d248c3fa029da68ce61fe735432b047e32179883bdb1e79ed9bb8195e"},
- {file = "cryptography-41.0.4-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:37480760ae08065437e6573d14be973112c9e6dcaf5f11d00147ee74f37a3829"},
- {file = "cryptography-41.0.4-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:b5f4dfe950ff0479f1f00eda09c18798d4f49b98f4e2006d644b3301682ebdca"},
- {file = "cryptography-41.0.4-pp38-pypy38_pp73-macosx_10_12_x86_64.whl", hash = "sha256:7e53db173370dea832190870e975a1e09c86a879b613948f09eb49324218c14d"},
- {file = "cryptography-41.0.4-pp38-pypy38_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:5b72205a360f3b6176485a333256b9bcd48700fc755fef51c8e7e67c4b63e3ac"},
- {file = "cryptography-41.0.4-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:93530900d14c37a46ce3d6c9e6fd35dbe5f5601bf6b3a5c325c7bffc030344d9"},
- {file = "cryptography-41.0.4-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:efc8ad4e6fc4f1752ebfb58aefece8b4e3c4cae940b0994d43649bdfce8d0d4f"},
- {file = "cryptography-41.0.4-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:c3391bd8e6de35f6f1140e50aaeb3e2b3d6a9012536ca23ab0d9c35ec18c8a91"},
- {file = "cryptography-41.0.4-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:0d9409894f495d465fe6fda92cb70e8323e9648af912d5b9141d616df40a87b8"},
- {file = "cryptography-41.0.4-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:8ac4f9ead4bbd0bc8ab2d318f97d85147167a488be0e08814a37eb2f439d5cf6"},
- {file = "cryptography-41.0.4-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:047c4603aeb4bbd8db2756e38f5b8bd7e94318c047cfe4efeb5d715e08b49311"},
- {file = "cryptography-41.0.4.tar.gz", hash = "sha256:7febc3094125fc126a7f6fb1f420d0da639f3f32cb15c8ff0dc3997c4549f51a"},
+ {file = "cryptography-41.0.6-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:0f27acb55a4e77b9be8d550d762b0513ef3fc658cd3eb15110ebbcbd626db12c"},
+ {file = "cryptography-41.0.6-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:ae236bb8760c1e55b7a39b6d4d32d2279bc6c7c8500b7d5a13b6fb9fc97be35b"},
+ {file = "cryptography-41.0.6-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:afda76d84b053923c27ede5edc1ed7d53e3c9f475ebaf63c68e69f1403c405a8"},
+ {file = "cryptography-41.0.6-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:da46e2b5df770070412c46f87bac0849b8d685c5f2679771de277a422c7d0b86"},
+ {file = "cryptography-41.0.6-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:ff369dd19e8fe0528b02e8df9f2aeb2479f89b1270d90f96a63500afe9af5cae"},
+ {file = "cryptography-41.0.6-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:b648fe2a45e426aaee684ddca2632f62ec4613ef362f4d681a9a6283d10e079d"},
+ {file = "cryptography-41.0.6-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:5daeb18e7886a358064a68dbcaf441c036cbdb7da52ae744e7b9207b04d3908c"},
+ {file = "cryptography-41.0.6-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:068bc551698c234742c40049e46840843f3d98ad7ce265fd2bd4ec0d11306596"},
+ {file = "cryptography-41.0.6-cp37-abi3-win32.whl", hash = "sha256:2132d5865eea673fe6712c2ed5fb4fa49dba10768bb4cc798345748380ee3660"},
+ {file = "cryptography-41.0.6-cp37-abi3-win_amd64.whl", hash = "sha256:48783b7e2bef51224020efb61b42704207dde583d7e371ef8fc2a5fb6c0aabc7"},
+ {file = "cryptography-41.0.6-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:8efb2af8d4ba9dbc9c9dd8f04d19a7abb5b49eab1f3694e7b5a16a5fc2856f5c"},
+ {file = "cryptography-41.0.6-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:c5a550dc7a3b50b116323e3d376241829fd326ac47bc195e04eb33a8170902a9"},
+ {file = "cryptography-41.0.6-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:85abd057699b98fce40b41737afb234fef05c67e116f6f3650782c10862c43da"},
+ {file = "cryptography-41.0.6-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:f39812f70fc5c71a15aa3c97b2bbe213c3f2a460b79bd21c40d033bb34a9bf36"},
+ {file = "cryptography-41.0.6-pp38-pypy38_pp73-macosx_10_12_x86_64.whl", hash = "sha256:742ae5e9a2310e9dade7932f9576606836ed174da3c7d26bc3d3ab4bd49b9f65"},
+ {file = "cryptography-41.0.6-pp38-pypy38_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:35f3f288e83c3f6f10752467c48919a7a94b7d88cc00b0668372a0d2ad4f8ead"},
+ {file = "cryptography-41.0.6-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:4d03186af98b1c01a4eda396b137f29e4e3fb0173e30f885e27acec8823c1b09"},
+ {file = "cryptography-41.0.6-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:b27a7fd4229abef715e064269d98a7e2909ebf92eb6912a9603c7e14c181928c"},
+ {file = "cryptography-41.0.6-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:398ae1fc711b5eb78e977daa3cbf47cec20f2c08c5da129b7a296055fbb22aed"},
+ {file = "cryptography-41.0.6-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:7e00fb556bda398b99b0da289ce7053639d33b572847181d6483ad89835115f6"},
+ {file = "cryptography-41.0.6-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:60e746b11b937911dc70d164060d28d273e31853bb359e2b2033c9e93e6f3c43"},
+ {file = "cryptography-41.0.6-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:3288acccef021e3c3c10d58933f44e8602cf04dba96d9796d70d537bb2f4bbc4"},
+ {file = "cryptography-41.0.6.tar.gz", hash = "sha256:422e3e31d63743855e43e5a6fcc8b4acab860f560f9321b0ee6269cc7ed70cc3"},
]
[package.dependencies]
diff --git a/pyproject.toml b/pyproject.toml
index fb610316d..d4d32381e 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,13 +1,13 @@
[tool.poetry]
name = "nautobot-ssot"
-version = "2.0.2"
+version = "2.1.0"
description = "Nautobot Single Source of Truth"
authors = ["Network to Code, LLC "]
license = "Apache-2.0"
readme = "README.md"
-homepage = "https://github.com/nautobot/nautobot-plugin-ssot/"
-repository = "https://github.com/nautobot/nautobot-plugin-ssot/"
-keywords = ["nautobot", "nautobot-plugin"]
+homepage = "https://github.com/nautobot/nautobot-app-ssot/"
+repository = "https://github.com/nautobot/nautobot-app-ssot/"
+keywords = ["nautobot", "nautobot-plugin", "nautobot-app"]
classifiers = [
"Intended Audience :: Developers",
"Development Status :: 5 - Production/Stable",
diff --git a/tasks.py b/tasks.py
index 93d33a4f4..0d27464ab 100644
--- a/tasks.py
+++ b/tasks.py
@@ -669,7 +669,7 @@ def unittest_coverage(context):
}
)
def tests(context, failfast=False, keepdb=False, lint_only=False):
- """Run all tests for this plugin."""
+ """Run all tests for this app."""
# 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_ssot.local):
print("Starting Docker Containers...")