nautobot 2.2.5__py3-none-any.whl → 2.2.7__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of nautobot might be problematic. Click here for more details.

Files changed (345) hide show
  1. nautobot/apps/api.py +2 -0
  2. nautobot/apps/models.py +2 -0
  3. nautobot/core/api/fields.py +13 -0
  4. nautobot/core/api/mixins.py +6 -1
  5. nautobot/core/api/schema.py +3 -1
  6. nautobot/core/api/serializers.py +7 -1
  7. nautobot/core/celery/__init__.py +1 -1
  8. nautobot/core/management/commands/generate_test_data.py +128 -158
  9. nautobot/core/models/fields.py +15 -0
  10. nautobot/core/tests/runner.py +10 -0
  11. nautobot/core/tests/test_utils.py +48 -1
  12. nautobot/core/utils/git.py +121 -49
  13. nautobot/core/utils/module_loading.py +10 -2
  14. nautobot/dcim/factory.py +1 -1
  15. nautobot/dcim/tests/test_models.py +2 -0
  16. nautobot/extras/datasources/git.py +133 -135
  17. nautobot/extras/datasources/utils.py +3 -0
  18. nautobot/extras/filters/__init__.py +1 -0
  19. nautobot/extras/forms/forms.py +16 -3
  20. nautobot/extras/jobs.py +9 -1
  21. nautobot/extras/migrations/0107_laxurlfield.py +28 -0
  22. nautobot/extras/migrations/0108_jobbutton_enabled.py +17 -0
  23. nautobot/extras/models/datasources.py +6 -4
  24. nautobot/extras/models/groups.py +9 -2
  25. nautobot/extras/models/jobs.py +30 -0
  26. nautobot/extras/models/models.py +2 -4
  27. nautobot/extras/tables.py +3 -0
  28. nautobot/extras/templates/extras/jobbutton_retrieve.html +6 -2
  29. nautobot/extras/templatetags/job_buttons.py +2 -2
  30. nautobot/extras/tests/git_data/01-valid-files/__init__.py +0 -0
  31. nautobot/extras/tests/git_data/01-valid-files/config_context_schemas/schema-1.yaml +18 -0
  32. nautobot/extras/tests/git_data/01-valid-files/config_contexts/context.yaml +12 -0
  33. nautobot/extras/tests/git_data/01-valid-files/config_contexts/devices/test-device.json +3 -0
  34. nautobot/extras/tests/git_data/01-valid-files/config_contexts/locations/Test Location.json +7 -0
  35. nautobot/extras/tests/git_data/01-valid-files/export_templates/dcim/device/template.j2 +3 -0
  36. nautobot/extras/tests/git_data/01-valid-files/export_templates/dcim/device/template2.html +4 -0
  37. nautobot/extras/tests/git_data/01-valid-files/export_templates/ipam/vlan/template.j2 +3 -0
  38. nautobot/extras/tests/git_data/01-valid-files/jobs/__init__.py +5 -0
  39. nautobot/extras/tests/git_data/01-valid-files/jobs/my_job.py +16 -0
  40. nautobot/extras/tests/git_data/02-invalid-files/__init__.py +0 -0
  41. nautobot/extras/tests/git_data/02-invalid-files/config_context_schemas/badschema1.json +2 -0
  42. nautobot/extras/tests/git_data/02-invalid-files/config_context_schemas/badschema2.json +1 -0
  43. nautobot/extras/tests/git_data/02-invalid-files/config_contexts/badcontext1.json +2 -0
  44. nautobot/extras/tests/git_data/02-invalid-files/config_contexts/badcontext2.json +1 -0
  45. nautobot/extras/tests/git_data/02-invalid-files/config_contexts/badcontext3.json +3 -0
  46. nautobot/extras/tests/git_data/02-invalid-files/config_contexts/devices/nosuchdevice.json +1 -0
  47. nautobot/extras/tests/git_data/02-invalid-files/dcim/template.j2 +0 -0
  48. nautobot/extras/tests/git_data/02-invalid-files/devices/template.j2 +0 -0
  49. nautobot/extras/tests/git_data/02-invalid-files/export_templates/dcim/nosuchmodel/template.j2 +3 -0
  50. nautobot/extras/tests/git_data/02-invalid-files/export_templates/nosuchapp/device/template.j2 +3 -0
  51. nautobot/extras/tests/git_data/02-invalid-files/jobs/__init__.py +2 -0
  52. nautobot/extras/tests/git_data/02-invalid-files/jobs/importerror.py +1 -0
  53. nautobot/extras/tests/git_data/02-invalid-files/jobs/syntaxerror.py +1 -0
  54. nautobot/extras/tests/git_helper.py +76 -0
  55. nautobot/extras/tests/test_api.py +28 -11
  56. nautobot/extras/tests/test_datasources.py +94 -276
  57. nautobot/extras/tests/test_dynamicgroups.py +8 -1
  58. nautobot/extras/tests/test_models.py +8 -3
  59. nautobot/extras/tests/test_views.py +21 -7
  60. nautobot/extras/views.py +1 -1
  61. nautobot/ipam/api/serializers.py +46 -16
  62. nautobot/ipam/api/views.py +29 -16
  63. nautobot/ipam/filters.py +9 -1
  64. nautobot/ipam/forms.py +8 -0
  65. nautobot/ipam/tables.py +1 -1
  66. nautobot/ipam/tests/test_api.py +15 -20
  67. nautobot/ipam/tests/test_filters.py +15 -0
  68. nautobot/project-static/docs/404.html +83 -8
  69. nautobot/project-static/docs/apps/index.html +96 -10
  70. nautobot/project-static/docs/apps/nautobot-apps.html +96 -10
  71. nautobot/project-static/docs/assets/app-icons/icon-CapacityMetrics.svg +1 -0
  72. nautobot/project-static/docs/assets/app-icons/icon-CircuitMaintenance.png +0 -0
  73. nautobot/project-static/docs/assets/javascripts/{bundle.ebd0bdb7.min.js → bundle.fe8b6f2b.min.js} +4 -4
  74. nautobot/project-static/docs/assets/javascripts/{bundle.ebd0bdb7.min.js.map → bundle.fe8b6f2b.min.js.map} +3 -3
  75. nautobot/project-static/docs/assets/javascripts/glightbox.min.js +1 -0
  76. nautobot/project-static/docs/assets/stylesheets/glightbox.min.css +1 -0
  77. nautobot/project-static/docs/code-reference/nautobot/apps/__init__.html +96 -10
  78. nautobot/project-static/docs/code-reference/nautobot/apps/admin.html +96 -10
  79. nautobot/project-static/docs/code-reference/nautobot/apps/api.html +96 -10
  80. nautobot/project-static/docs/code-reference/nautobot/apps/change_logging.html +96 -10
  81. nautobot/project-static/docs/code-reference/nautobot/apps/choices.html +96 -10
  82. nautobot/project-static/docs/code-reference/nautobot/apps/config.html +96 -10
  83. nautobot/project-static/docs/code-reference/nautobot/apps/constants.html +96 -10
  84. nautobot/project-static/docs/code-reference/nautobot/apps/datasources.html +96 -10
  85. nautobot/project-static/docs/code-reference/nautobot/apps/exceptions.html +96 -10
  86. nautobot/project-static/docs/code-reference/nautobot/apps/factory.html +96 -10
  87. nautobot/project-static/docs/code-reference/nautobot/apps/filters.html +96 -10
  88. nautobot/project-static/docs/code-reference/nautobot/apps/forms.html +96 -10
  89. nautobot/project-static/docs/code-reference/nautobot/apps/graphql.html +96 -10
  90. nautobot/project-static/docs/code-reference/nautobot/apps/jobs.html +96 -10
  91. nautobot/project-static/docs/code-reference/nautobot/apps/models.html +156 -12
  92. nautobot/project-static/docs/code-reference/nautobot/apps/querysets.html +96 -10
  93. nautobot/project-static/docs/code-reference/nautobot/apps/secrets.html +96 -10
  94. nautobot/project-static/docs/code-reference/nautobot/apps/tables.html +96 -10
  95. nautobot/project-static/docs/code-reference/nautobot/apps/testing.html +106 -11
  96. nautobot/project-static/docs/code-reference/nautobot/apps/ui.html +96 -10
  97. nautobot/project-static/docs/code-reference/nautobot/apps/urls.html +96 -10
  98. nautobot/project-static/docs/code-reference/nautobot/apps/utils.html +143 -11
  99. nautobot/project-static/docs/code-reference/nautobot/apps/views.html +96 -10
  100. nautobot/project-static/docs/development/apps/api/configuration-view.html +96 -10
  101. nautobot/project-static/docs/development/apps/api/database-backend-config.html +96 -10
  102. nautobot/project-static/docs/development/apps/api/models/django-admin.html +96 -10
  103. nautobot/project-static/docs/development/apps/api/models/global-search.html +96 -10
  104. nautobot/project-static/docs/development/apps/api/models/graphql.html +96 -10
  105. nautobot/project-static/docs/development/apps/api/models/index.html +96 -10
  106. nautobot/project-static/docs/development/apps/api/nautobot-app-config.html +96 -10
  107. nautobot/project-static/docs/development/apps/api/platform-features/custom-validators.html +96 -10
  108. nautobot/project-static/docs/development/apps/api/platform-features/filter-extensions.html +96 -10
  109. nautobot/project-static/docs/development/apps/api/platform-features/git-repository-content.html +96 -10
  110. nautobot/project-static/docs/development/apps/api/platform-features/index.html +96 -10
  111. nautobot/project-static/docs/development/apps/api/platform-features/jinja2-filters.html +96 -10
  112. nautobot/project-static/docs/development/apps/api/platform-features/jobs.html +96 -10
  113. nautobot/project-static/docs/development/apps/api/platform-features/populating-extensibility-features.html +96 -10
  114. nautobot/project-static/docs/development/apps/api/platform-features/secrets-providers.html +96 -10
  115. nautobot/project-static/docs/development/apps/api/platform-features/uniquely-identify-objects.html +96 -10
  116. nautobot/project-static/docs/development/apps/api/prometheus.html +96 -10
  117. nautobot/project-static/docs/development/apps/api/setup.html +96 -10
  118. nautobot/project-static/docs/development/apps/api/testing.html +96 -10
  119. nautobot/project-static/docs/development/apps/api/ui-extensions/banners.html +96 -10
  120. nautobot/project-static/docs/development/apps/api/ui-extensions/home-page.html +96 -10
  121. nautobot/project-static/docs/development/apps/api/ui-extensions/index.html +96 -10
  122. nautobot/project-static/docs/development/apps/api/ui-extensions/navigation.html +96 -10
  123. nautobot/project-static/docs/development/apps/api/ui-extensions/object-views.html +96 -10
  124. nautobot/project-static/docs/development/apps/api/views/base-template.html +96 -10
  125. nautobot/project-static/docs/development/apps/api/views/core-view-overrides.html +96 -10
  126. nautobot/project-static/docs/development/apps/api/views/django-generic-views.html +96 -10
  127. nautobot/project-static/docs/development/apps/api/views/help-documentation.html +96 -10
  128. nautobot/project-static/docs/development/apps/api/views/index.html +96 -10
  129. nautobot/project-static/docs/development/apps/api/views/nautobot-generic-views.html +96 -10
  130. nautobot/project-static/docs/development/apps/api/views/nautobotuiviewset.html +96 -10
  131. nautobot/project-static/docs/development/apps/api/views/nautobotuiviewsetrouter.html +96 -10
  132. nautobot/project-static/docs/development/apps/api/views/notes.html +96 -10
  133. nautobot/project-static/docs/development/apps/api/views/rest-api.html +96 -10
  134. nautobot/project-static/docs/development/apps/api/views/urls.html +96 -10
  135. nautobot/project-static/docs/development/apps/index.html +96 -10
  136. nautobot/project-static/docs/development/apps/migration/code-updates.html +97 -11
  137. nautobot/project-static/docs/development/apps/migration/dependency-updates.html +96 -10
  138. nautobot/project-static/docs/development/apps/migration/from-v1.html +96 -10
  139. nautobot/project-static/docs/development/apps/migration/model-updates/dcim.html +96 -10
  140. nautobot/project-static/docs/development/apps/migration/model-updates/extras.html +96 -10
  141. nautobot/project-static/docs/development/apps/migration/model-updates/global.html +96 -10
  142. nautobot/project-static/docs/development/apps/migration/model-updates/ipam.html +96 -10
  143. nautobot/project-static/docs/development/apps/porting-from-netbox.html +96 -10
  144. nautobot/project-static/docs/development/core/application-registry.html +96 -10
  145. nautobot/project-static/docs/development/core/best-practices.html +96 -10
  146. nautobot/project-static/docs/development/core/bootstrap-ui.html +96 -10
  147. nautobot/project-static/docs/development/core/caching.html +96 -10
  148. nautobot/project-static/docs/development/core/controllers.html +96 -10
  149. nautobot/project-static/docs/development/core/docker-compose-advanced-use-cases.html +96 -10
  150. nautobot/project-static/docs/development/core/generic-views.html +96 -10
  151. nautobot/project-static/docs/development/core/getting-started.html +98 -13
  152. nautobot/project-static/docs/development/core/homepage.html +96 -10
  153. nautobot/project-static/docs/development/core/index.html +96 -10
  154. nautobot/project-static/docs/development/core/model-checklist.html +96 -10
  155. nautobot/project-static/docs/development/core/model-features.html +96 -10
  156. nautobot/project-static/docs/development/core/natural-keys.html +96 -10
  157. nautobot/project-static/docs/development/core/navigation-menu.html +96 -10
  158. nautobot/project-static/docs/development/core/release-checklist.html +96 -10
  159. nautobot/project-static/docs/development/core/role-internals.html +96 -10
  160. nautobot/project-static/docs/development/core/settings.html +96 -10
  161. nautobot/project-static/docs/development/core/style-guide.html +96 -10
  162. nautobot/project-static/docs/development/core/templates.html +96 -10
  163. nautobot/project-static/docs/development/core/testing.html +109 -11
  164. nautobot/project-static/docs/development/core/user-preferences.html +96 -10
  165. nautobot/project-static/docs/development/index.html +96 -10
  166. nautobot/project-static/docs/development/jobs/index.html +96 -10
  167. nautobot/project-static/docs/development/jobs/migration/from-v1.html +96 -10
  168. nautobot/project-static/docs/index.html +13 -8362
  169. nautobot/project-static/docs/objects.inv +0 -0
  170. nautobot/project-static/docs/overview/application_stack.html +8229 -0
  171. nautobot/project-static/docs/overview/design_philosophy.html +8158 -0
  172. nautobot/project-static/docs/overview/index.html +8230 -0
  173. nautobot/project-static/docs/release-notes/index.html +96 -10
  174. nautobot/project-static/docs/release-notes/version-1.0.html +96 -10
  175. nautobot/project-static/docs/release-notes/version-1.1.html +96 -10
  176. nautobot/project-static/docs/release-notes/version-1.2.html +96 -10
  177. nautobot/project-static/docs/release-notes/version-1.3.html +96 -10
  178. nautobot/project-static/docs/release-notes/version-1.4.html +96 -10
  179. nautobot/project-static/docs/release-notes/version-1.5.html +96 -10
  180. nautobot/project-static/docs/release-notes/version-1.6.html +96 -10
  181. nautobot/project-static/docs/release-notes/version-2.0.html +96 -10
  182. nautobot/project-static/docs/release-notes/version-2.1.html +96 -10
  183. nautobot/project-static/docs/release-notes/version-2.2.html +516 -131
  184. nautobot/project-static/docs/requirements.txt +2 -1
  185. nautobot/project-static/docs/search/search_index.json +1 -1
  186. nautobot/project-static/docs/sitemap.xml +268 -258
  187. nautobot/project-static/docs/sitemap.xml.gz +0 -0
  188. nautobot/project-static/docs/user-guide/administration/configuration/authentication/ldap.html +96 -10
  189. nautobot/project-static/docs/user-guide/administration/configuration/authentication/remote.html +96 -10
  190. nautobot/project-static/docs/user-guide/administration/configuration/authentication/sso.html +96 -10
  191. nautobot/project-static/docs/user-guide/administration/configuration/index.html +96 -10
  192. nautobot/project-static/docs/user-guide/administration/configuration/optional-settings.html +96 -10
  193. nautobot/project-static/docs/user-guide/administration/configuration/required-settings.html +96 -10
  194. nautobot/project-static/docs/user-guide/administration/configuration/time-zones.html +96 -10
  195. nautobot/project-static/docs/user-guide/administration/guides/caching.html +96 -10
  196. nautobot/project-static/docs/user-guide/administration/guides/celery-queues.html +96 -10
  197. nautobot/project-static/docs/user-guide/administration/guides/healthcheck.html +96 -10
  198. nautobot/project-static/docs/user-guide/administration/guides/permissions.html +96 -10
  199. nautobot/project-static/docs/user-guide/administration/guides/prometheus-metrics.html +96 -10
  200. nautobot/project-static/docs/user-guide/administration/guides/replicating-nautobot.html +96 -10
  201. nautobot/project-static/docs/user-guide/administration/guides/request-profiling.html +96 -10
  202. nautobot/project-static/docs/user-guide/administration/guides/s3-django-storage.html +96 -10
  203. nautobot/project-static/docs/user-guide/administration/installation/app-install.html +96 -10
  204. nautobot/project-static/docs/user-guide/administration/installation/external-authentication.html +96 -10
  205. nautobot/project-static/docs/user-guide/administration/installation/http-server.html +96 -10
  206. nautobot/project-static/docs/user-guide/administration/installation/index.html +96 -10
  207. nautobot/project-static/docs/user-guide/administration/installation/install_system.html +96 -10
  208. nautobot/project-static/docs/user-guide/administration/installation/nautobot.html +96 -10
  209. nautobot/project-static/docs/user-guide/administration/installation/services.html +96 -10
  210. nautobot/project-static/docs/user-guide/administration/installation-extras/docker.html +96 -10
  211. nautobot/project-static/docs/user-guide/administration/installation-extras/health-checks.html +96 -10
  212. nautobot/project-static/docs/user-guide/administration/installation-extras/selinux-troubleshooting.html +96 -10
  213. nautobot/project-static/docs/user-guide/administration/migration/migrating-from-netbox.html +96 -10
  214. nautobot/project-static/docs/user-guide/administration/migration/migrating-from-postgresql.html +96 -10
  215. nautobot/project-static/docs/user-guide/administration/tools/nautobot-server.html +96 -10
  216. nautobot/project-static/docs/user-guide/administration/tools/nautobot-shell.html +96 -10
  217. nautobot/project-static/docs/user-guide/administration/upgrading/database-backup.html +96 -10
  218. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/after-you-upgrade.html +96 -10
  219. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/before-you-upgrade.html +96 -10
  220. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/for-developers.html +96 -10
  221. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/index.html +96 -10
  222. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/whats-changed.html +96 -10
  223. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/region-and-site-data-migration-guide.html +96 -10
  224. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/tables/v2-code-location-changes.yaml +1 -1
  225. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/upgrading-from-nautobot-v1.html +97 -11
  226. nautobot/project-static/docs/user-guide/administration/upgrading/upgrading.html +96 -10
  227. nautobot/project-static/docs/user-guide/core-data-model/circuits/circuit.html +96 -10
  228. nautobot/project-static/docs/user-guide/core-data-model/circuits/circuittermination.html +96 -10
  229. nautobot/project-static/docs/user-guide/core-data-model/circuits/circuittype.html +96 -10
  230. nautobot/project-static/docs/user-guide/core-data-model/circuits/provider.html +96 -10
  231. nautobot/project-static/docs/user-guide/core-data-model/circuits/providernetwork.html +96 -10
  232. nautobot/project-static/docs/user-guide/core-data-model/dcim/cable.html +96 -10
  233. nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleport.html +96 -10
  234. nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleporttemplate.html +96 -10
  235. nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleserverport.html +96 -10
  236. nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleserverporttemplate.html +96 -10
  237. nautobot/project-static/docs/user-guide/core-data-model/dcim/controller.html +96 -10
  238. nautobot/project-static/docs/user-guide/core-data-model/dcim/controllermanageddevicegroup.html +96 -10
  239. nautobot/project-static/docs/user-guide/core-data-model/dcim/device.html +96 -10
  240. nautobot/project-static/docs/user-guide/core-data-model/dcim/devicebay.html +96 -10
  241. nautobot/project-static/docs/user-guide/core-data-model/dcim/devicebaytemplate.html +96 -10
  242. nautobot/project-static/docs/user-guide/core-data-model/dcim/devicefamily.html +96 -10
  243. nautobot/project-static/docs/user-guide/core-data-model/dcim/deviceredundancygroup.html +96 -10
  244. nautobot/project-static/docs/user-guide/core-data-model/dcim/devicetype.html +96 -10
  245. nautobot/project-static/docs/user-guide/core-data-model/dcim/frontport.html +96 -10
  246. nautobot/project-static/docs/user-guide/core-data-model/dcim/frontporttemplate.html +96 -10
  247. nautobot/project-static/docs/user-guide/core-data-model/dcim/interface.html +96 -10
  248. nautobot/project-static/docs/user-guide/core-data-model/dcim/interfaceredundancygroup.html +96 -10
  249. nautobot/project-static/docs/user-guide/core-data-model/dcim/interfacetemplate.html +96 -10
  250. nautobot/project-static/docs/user-guide/core-data-model/dcim/inventoryitem.html +96 -10
  251. nautobot/project-static/docs/user-guide/core-data-model/dcim/location.html +96 -10
  252. nautobot/project-static/docs/user-guide/core-data-model/dcim/locationtype.html +96 -10
  253. nautobot/project-static/docs/user-guide/core-data-model/dcim/manufacturer.html +96 -10
  254. nautobot/project-static/docs/user-guide/core-data-model/dcim/platform.html +96 -10
  255. nautobot/project-static/docs/user-guide/core-data-model/dcim/powerfeed.html +96 -10
  256. nautobot/project-static/docs/user-guide/core-data-model/dcim/poweroutlet.html +96 -10
  257. nautobot/project-static/docs/user-guide/core-data-model/dcim/poweroutlettemplate.html +96 -10
  258. nautobot/project-static/docs/user-guide/core-data-model/dcim/powerpanel.html +96 -10
  259. nautobot/project-static/docs/user-guide/core-data-model/dcim/powerport.html +96 -10
  260. nautobot/project-static/docs/user-guide/core-data-model/dcim/powerporttemplate.html +96 -10
  261. nautobot/project-static/docs/user-guide/core-data-model/dcim/rack.html +96 -10
  262. nautobot/project-static/docs/user-guide/core-data-model/dcim/rackgroup.html +96 -10
  263. nautobot/project-static/docs/user-guide/core-data-model/dcim/rackreservation.html +96 -10
  264. nautobot/project-static/docs/user-guide/core-data-model/dcim/rearport.html +96 -10
  265. nautobot/project-static/docs/user-guide/core-data-model/dcim/rearporttemplate.html +96 -10
  266. nautobot/project-static/docs/user-guide/core-data-model/dcim/softwareimagefile.html +96 -10
  267. nautobot/project-static/docs/user-guide/core-data-model/dcim/softwareversion.html +96 -10
  268. nautobot/project-static/docs/user-guide/core-data-model/dcim/virtualchassis.html +96 -10
  269. nautobot/project-static/docs/user-guide/core-data-model/extras/configcontext.html +96 -10
  270. nautobot/project-static/docs/user-guide/core-data-model/extras/configcontextschema.html +96 -10
  271. nautobot/project-static/docs/user-guide/core-data-model/extras/contact.html +96 -10
  272. nautobot/project-static/docs/user-guide/core-data-model/extras/team.html +96 -10
  273. nautobot/project-static/docs/user-guide/core-data-model/ipam/ipaddress.html +96 -10
  274. nautobot/project-static/docs/user-guide/core-data-model/ipam/namespace.html +96 -10
  275. nautobot/project-static/docs/user-guide/core-data-model/ipam/prefix.html +96 -10
  276. nautobot/project-static/docs/user-guide/core-data-model/ipam/rir.html +96 -10
  277. nautobot/project-static/docs/user-guide/core-data-model/ipam/routetarget.html +96 -10
  278. nautobot/project-static/docs/user-guide/core-data-model/ipam/service.html +96 -10
  279. nautobot/project-static/docs/user-guide/core-data-model/ipam/vlan.html +96 -10
  280. nautobot/project-static/docs/user-guide/core-data-model/ipam/vlangroup.html +96 -10
  281. nautobot/project-static/docs/user-guide/core-data-model/ipam/vrf.html +96 -10
  282. nautobot/project-static/docs/user-guide/core-data-model/overview/introduction.html +96 -10
  283. nautobot/project-static/docs/user-guide/core-data-model/tenancy/tenant.html +96 -10
  284. nautobot/project-static/docs/user-guide/core-data-model/tenancy/tenantgroup.html +96 -10
  285. nautobot/project-static/docs/user-guide/core-data-model/virtualization/cluster.html +96 -10
  286. nautobot/project-static/docs/user-guide/core-data-model/virtualization/clustergroup.html +96 -10
  287. nautobot/project-static/docs/user-guide/core-data-model/virtualization/clustertype.html +96 -10
  288. nautobot/project-static/docs/user-guide/core-data-model/virtualization/virtualmachine.html +96 -10
  289. nautobot/project-static/docs/user-guide/core-data-model/virtualization/vminterface.html +96 -10
  290. nautobot/project-static/docs/user-guide/feature-guides/contacts-and-teams.html +96 -10
  291. nautobot/project-static/docs/user-guide/feature-guides/custom-fields.html +96 -10
  292. nautobot/project-static/docs/user-guide/feature-guides/getting-started/creating-devices.html +96 -10
  293. nautobot/project-static/docs/user-guide/feature-guides/getting-started/creating-location-types-and-locations.html +96 -10
  294. nautobot/project-static/docs/user-guide/feature-guides/getting-started/index.html +96 -10
  295. nautobot/project-static/docs/user-guide/feature-guides/getting-started/interfaces.html +96 -10
  296. nautobot/project-static/docs/user-guide/feature-guides/getting-started/ipam.html +96 -10
  297. nautobot/project-static/docs/user-guide/feature-guides/getting-started/platforms.html +96 -10
  298. nautobot/project-static/docs/user-guide/feature-guides/getting-started/search-bar.html +96 -10
  299. nautobot/project-static/docs/user-guide/feature-guides/getting-started/tenants.html +96 -10
  300. nautobot/project-static/docs/user-guide/feature-guides/getting-started/vlans-and-vlan-groups.html +96 -10
  301. nautobot/project-static/docs/user-guide/feature-guides/git-data-source.html +96 -10
  302. nautobot/project-static/docs/user-guide/feature-guides/graphql.html +96 -10
  303. nautobot/project-static/docs/user-guide/feature-guides/ip-address-merge-tool.html +96 -10
  304. nautobot/project-static/docs/user-guide/feature-guides/relationships.html +96 -10
  305. nautobot/project-static/docs/user-guide/feature-guides/software-image-files-and-versions.html +96 -10
  306. nautobot/project-static/docs/user-guide/index.html +99 -13
  307. nautobot/project-static/docs/user-guide/platform-functionality/change-logging.html +96 -10
  308. nautobot/project-static/docs/user-guide/platform-functionality/computedfield.html +96 -10
  309. nautobot/project-static/docs/user-guide/platform-functionality/customfield.html +96 -10
  310. nautobot/project-static/docs/user-guide/platform-functionality/customlink.html +96 -10
  311. nautobot/project-static/docs/user-guide/platform-functionality/dynamicgroup.html +96 -10
  312. nautobot/project-static/docs/user-guide/platform-functionality/exporttemplate.html +96 -10
  313. nautobot/project-static/docs/user-guide/platform-functionality/externalintegration.html +96 -10
  314. nautobot/project-static/docs/user-guide/platform-functionality/gitrepository.html +96 -10
  315. nautobot/project-static/docs/user-guide/platform-functionality/graphql.html +96 -10
  316. nautobot/project-static/docs/user-guide/platform-functionality/graphqlquery.html +96 -10
  317. nautobot/project-static/docs/user-guide/platform-functionality/imageattachment.html +96 -10
  318. nautobot/project-static/docs/user-guide/platform-functionality/jobs/index.html +97 -11
  319. nautobot/project-static/docs/user-guide/platform-functionality/jobs/job-scheduling-and-approvals.html +96 -10
  320. nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobbutton.html +101 -14
  321. nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobhook.html +96 -10
  322. nautobot/project-static/docs/user-guide/platform-functionality/jobs/models.html +96 -10
  323. nautobot/project-static/docs/user-guide/platform-functionality/napalm.html +96 -10
  324. nautobot/project-static/docs/user-guide/platform-functionality/note.html +96 -10
  325. nautobot/project-static/docs/user-guide/platform-functionality/relationship.html +96 -10
  326. nautobot/project-static/docs/user-guide/platform-functionality/rest-api/authentication.html +96 -10
  327. nautobot/project-static/docs/user-guide/platform-functionality/rest-api/filtering.html +96 -10
  328. nautobot/project-static/docs/user-guide/platform-functionality/rest-api/overview.html +96 -10
  329. nautobot/project-static/docs/user-guide/platform-functionality/rest-api/ui-related-endpoints.html +96 -10
  330. nautobot/project-static/docs/user-guide/platform-functionality/role.html +96 -10
  331. nautobot/project-static/docs/user-guide/platform-functionality/secret.html +96 -10
  332. nautobot/project-static/docs/user-guide/platform-functionality/status.html +96 -10
  333. nautobot/project-static/docs/user-guide/platform-functionality/tag.html +96 -10
  334. nautobot/project-static/docs/user-guide/platform-functionality/template-filters.html +96 -10
  335. nautobot/project-static/docs/user-guide/platform-functionality/users/objectpermission.html +96 -10
  336. nautobot/project-static/docs/user-guide/platform-functionality/users/token.html +96 -10
  337. nautobot/project-static/docs/user-guide/platform-functionality/webhook.html +96 -10
  338. nautobot/project-static/js/connection_toggles.js +7 -6
  339. {nautobot-2.2.5.dist-info → nautobot-2.2.7.dist-info}/METADATA +2 -2
  340. {nautobot-2.2.5.dist-info → nautobot-2.2.7.dist-info}/RECORD +344 -311
  341. nautobot/extras/tests/test_git.py +0 -23
  342. {nautobot-2.2.5.dist-info → nautobot-2.2.7.dist-info}/LICENSE.txt +0 -0
  343. {nautobot-2.2.5.dist-info → nautobot-2.2.7.dist-info}/NOTICE +0 -0
  344. {nautobot-2.2.5.dist-info → nautobot-2.2.7.dist-info}/WHEEL +0 -0
  345. {nautobot-2.2.5.dist-info → nautobot-2.2.7.dist-info}/entry_points.txt +0 -0
@@ -3,6 +3,7 @@
3
3
  from collections import namedtuple
4
4
  import logging
5
5
  import os
6
+ import string
6
7
 
7
8
  from git import Repo
8
9
 
@@ -34,8 +35,8 @@ GIT_ENVIRONMENT = {
34
35
 
35
36
  def swap_status_initials(data):
36
37
  """Swap Git status initials with its equivalent."""
37
- initial, text = data.split("\t")
38
- return GitDiffLog(status=GIT_STATUS_MAP.get(initial), text=text)
38
+ initial, text = data.split("\t", 1)
39
+ return GitDiffLog(status=GIT_STATUS_MAP.get(initial[0]), text=text)
39
40
 
40
41
 
41
42
  def convert_git_diff_log_to_list(logs):
@@ -93,69 +94,140 @@ class GitRepo:
93
94
  """
94
95
  Check out the given branch, and optionally the specified commit within that branch.
95
96
 
97
+ Args:
98
+ branch (str): A branch name, a tag name, or a (possibly abbreviated) commit identifier.
99
+ commit_hexsha (str): A specific (possibly abbreviated) commit identifier.
100
+
101
+ If `commit_hexsha` is specified and `branch` is either a tag or a commit identifier, they must match.
102
+ If `commit_hexsha` is specified and `branch` is a branch name, it must contain the specified commit.
103
+
96
104
  Returns:
97
- (str, bool): commit_hexsha the repo contains now, whether any change occurred
105
+ (str, bool): commit_hexsha the repo contains now, and whether any change occurred
98
106
  """
99
107
  # Short-circuit logic - do we already have this commit checked out?
100
- if commit_hexsha and commit_hexsha == self.head:
108
+ if commit_hexsha and self.head.startswith(commit_hexsha):
101
109
  logger.debug(f"Commit {commit_hexsha} is already checked out.")
102
- return (commit_hexsha, False)
110
+ return (self.head, False)
111
+ # User might specify the commit as a "branch" name...
112
+ if not commit_hexsha and set(branch).issubset(string.hexdigits) and self.head.startswith(branch):
113
+ logger.debug("Commit %s is already checked out.", branch)
114
+ return (self.head, False)
103
115
 
104
116
  self.fetch()
105
- if commit_hexsha:
106
- # Sanity check - GitPython doesn't provide a handy API for this so we just call a raw Git command:
107
- # $ git branch origin/<branch> --remotes --contains <commit>
108
- # prints the branch name if it DOES contain the commit, and nothing if it DOES NOT contain the commit.
109
- # Since we did a `fetch` and not a `pull` above, we need to check for the commit in the remote origin
110
- # branch, not the local (not-yet-updated) branch.
111
- if branch not in self.repo.git.branch(f"origin/{branch}", "--remotes", "--contains", commit_hexsha):
112
- raise RuntimeError(f"Requested to check out commit `{commit_hexsha}`, but it's not in branch {branch}!")
113
- logger.info(f"Checking out commit `{commit_hexsha}` on branch `{branch}`...")
114
- self.repo.git.checkout(commit_hexsha)
115
- return (commit_hexsha, True)
116
-
117
- if branch in self.repo.heads:
118
- branch_head = self.repo.heads[branch]
119
- else:
120
- try:
121
- branch_head = self.repo.create_head(branch, self.repo.remotes.origin.refs[branch])
122
- branch_head.set_tracking_branch(self.repo.remotes.origin.refs[branch])
123
- except IndexError as git_error:
124
- logger.error(
125
- "Branch %s does not exist at %s. %s", branch, next(iter(self.repo.remotes.origin.urls)), git_error
126
- )
127
- raise BranchDoesNotExist(
128
- f"Please create branch '{branch}' in upstream and try again."
129
- f" If this is a new repo, please add a commit before syncing. {git_error}"
117
+ # Is `branch` actually a branch, a tag, or a commit? Heuristics:
118
+ is_branch = branch in self.repo.remotes.origin.refs
119
+ is_tag = branch in self.repo.tags
120
+ maybe_commit = set(branch).issubset(string.hexdigits)
121
+ logger.debug(
122
+ "Branch %s --> is_branch: %s, is_tag: %s, maybe_commit: %s",
123
+ branch,
124
+ is_branch,
125
+ is_tag,
126
+ maybe_commit,
127
+ )
128
+
129
+ if is_branch:
130
+ if commit_hexsha:
131
+ # Sanity check - GitPython doesn't provide a handy API for this so we just call a raw Git command:
132
+ # $ git branch origin/<branch> --remotes --contains <commit>
133
+ # prints the branch name if it DOES contain the commit, and nothing if it DOES NOT contain the commit.
134
+ # Since we did a `fetch` and not a `pull` above, we need to check for the commit in the remote origin
135
+ # branch, not the local (not-yet-updated) branch.
136
+ if branch not in self.repo.git.branch(f"origin/{branch}", "--remotes", "--contains", commit_hexsha):
137
+ raise RuntimeError(
138
+ f"Requested to check out commit {commit_hexsha}, but it's not part of branch {branch}!"
139
+ )
140
+ logger.info("Checking out commit %s on branch %s...", commit_hexsha, branch)
141
+ self.repo.git.checkout(commit_hexsha)
142
+ return (self.head, True)
143
+
144
+ if branch in self.repo.heads:
145
+ branch_head = self.repo.heads[branch]
146
+ else:
147
+ try:
148
+ branch_head = self.repo.create_head(branch, self.repo.remotes.origin.refs[branch])
149
+ branch_head.set_tracking_branch(self.repo.remotes.origin.refs[branch])
150
+ except IndexError as git_error:
151
+ logger.error(
152
+ "Branch %s does not exist at %s. %s",
153
+ branch,
154
+ next(iter(self.repo.remotes.origin.urls)),
155
+ git_error,
156
+ )
157
+ raise BranchDoesNotExist(
158
+ f"Please create branch '{branch}' in upstream and try again."
159
+ f" If this is a new repo, please add a commit before syncing. {git_error}"
160
+ )
161
+
162
+ logger.info("Checking out latest commit on branch %s...", branch)
163
+ branch_head.checkout()
164
+ # No specific commit hash was given, so make sure we get the latest from origin
165
+ # We would use repo.remotes.origin.pull() here, but that will fail in the case where someone has
166
+ # force-pushed to the upstream repo since the last time we did a pull. To be safe, we reset instead.
167
+ self.repo.head.reset(f"origin/{branch}", index=True, working_tree=True)
168
+ logger.info("Latest commit on branch `%s` is `%s`", branch, self.head)
169
+ return (self.head, True)
170
+
171
+ if is_tag:
172
+ tag = self.repo.tags[branch]
173
+ if commit_hexsha:
174
+ # Sanity check
175
+ if not tag.commit.hexsha.startswith(commit_hexsha):
176
+ raise RuntimeError(
177
+ f"Requested to check out tag {branch} and commit {commit_hexsha} together, "
178
+ f"but tag {branch} is actually commit {tag.commit.hexsha}!"
179
+ )
180
+ logger.info("Checking out tag %s...", branch)
181
+ self.repo.git.checkout(branch)
182
+ return (self.head, True)
183
+
184
+ if maybe_commit:
185
+ # Sanity check
186
+ if commit_hexsha and not (commit_hexsha.startswith(branch) or branch.startswith(commit_hexsha)):
187
+ raise RuntimeError(
188
+ f"Requested to check out both {branch} and {commit_hexsha} together, "
189
+ f"but {branch} is neither a branch, a tag, nor the same commit hash!"
130
190
  )
131
191
 
132
- logger.info(f"Checking out latest commit on branch `{branch}`...")
133
- branch_head.checkout()
134
- # No specific commit hash was given, so make sure we get the latest from origin
135
- # We would use repo.remotes.origin.pull() here, but that will fail in the case where someone has
136
- # force-pushed to the upstream repo since the last time we did a pull. To be safe, we reset instead.
137
- self.repo.head.reset(f"origin/{branch}", index=True, working_tree=True)
138
- commit_hexsha = self.repo.head.reference.commit.hexsha
139
- logger.info(f"Latest commit on branch `{branch}` is `{commit_hexsha}`")
140
- return (commit_hexsha, True)
192
+ logger.info("Checking out commit %s...", branch)
193
+ self.repo.git.checkout(branch)
194
+ return (self.head, True)
195
+
196
+ # Fallthru
197
+ raise BranchDoesNotExist(
198
+ f"{branch} does not appear to be an existing branch, tag, or possible commit hash. "
199
+ "Please check your upstream repository and the data you are using."
200
+ )
141
201
 
142
202
  def diff_remote(self, branch):
143
203
  logger.debug("Fetching from remote.")
144
204
  self.fetch()
145
-
146
- try:
147
- self.repo.remotes.origin.refs[branch]
148
- except IndexError as git_error:
149
- logger.error(
150
- "Branch %s does not exist at %s. %s", branch, next(iter(self.repo.remotes.origin.urls)), git_error
151
- )
205
+ # Is `branch` actually a branch, a tag, or a commit? Heuristics:
206
+ is_branch = branch in self.repo.remotes.origin.refs
207
+ is_tag = branch in self.repo.tags
208
+ maybe_commit = set(branch).issubset(string.hexdigits)
209
+ logger.debug(
210
+ "Branch %s --> is_branch: %s, is_tag: %s, maybe_commit: %s",
211
+ branch,
212
+ is_branch,
213
+ is_tag,
214
+ maybe_commit,
215
+ )
216
+
217
+ if not is_branch and not is_tag and not maybe_commit:
218
+ logger.error("Branch %s does not exist at %s", branch, next(iter(self.repo.remotes.origin.urls)))
152
219
  raise BranchDoesNotExist(
153
220
  f"Please create branch '{branch}' in upstream and try again."
154
- f" If this is a new repo, please add a commit before syncing. {git_error}"
221
+ f" If this is a new repo, please add a commit before syncing."
155
222
  )
156
223
 
157
- logger.debug("Getting diff between local branch and remote branch")
158
- diff = self.repo.git.diff("--name-status", f"origin/{branch}")
224
+ if is_branch:
225
+ logger.debug("Getting diff between local branch and remote branch")
226
+ diff = self.repo.git.diff("--name-status", f"origin/{branch}")
227
+ else:
228
+ logger.debug("Getting diff between local state and specified tag or commit")
229
+ diff = self.repo.git.diff("--name-status", branch)
230
+
159
231
  if diff: # if diff is not empty
160
232
  return convert_git_diff_log_to_list(diff)
161
233
  logger.debug("No Difference")
@@ -24,6 +24,15 @@ def _temporarily_add_to_sys_path(path):
24
24
  sys.path = old_sys_path
25
25
 
26
26
 
27
+ def clear_module_from_sys_modules(module_name):
28
+ """
29
+ Remove the module and all its submodules from sys.modules.
30
+ """
31
+ for name in list(sys.modules.keys()):
32
+ if name == module_name or name.startswith(f"{module_name}."):
33
+ del sys.modules[name]
34
+
35
+
27
36
  def import_modules_privately(path, module_path=None, ignore_import_errors=True):
28
37
  """
29
38
  Import modules from the filesystem without adding the path permanently to `sys.path`.
@@ -69,7 +78,7 @@ def import_modules_privately(path, module_path=None, ignore_import_errors=True):
69
78
  continue
70
79
 
71
80
  if discovered_module_name in sys.modules:
72
- del sys.modules[discovered_module_name]
81
+ clear_module_from_sys_modules(discovered_module_name)
73
82
 
74
83
  try:
75
84
  if not is_package:
@@ -81,7 +90,6 @@ def import_modules_privately(path, module_path=None, ignore_import_errors=True):
81
90
  spec.loader.exec_module(module)
82
91
  else:
83
92
  module = importlib.import_module(discovered_module_name)
84
-
85
93
  importlib.reload(module)
86
94
  except Exception as exc:
87
95
  logger.error("Unable to load module %s from %s: %s", discovered_module_name, path, exc)
nautobot/dcim/factory.py CHANGED
@@ -708,6 +708,6 @@ class ControllerManagedDeviceGroupFactory(PrimaryModelFactory):
708
708
  name = UniqueFaker("word")
709
709
  parent = factory.Maybe("has_parent", random_instance(ControllerManagedDeviceGroup), None)
710
710
  controller = factory.LazyAttribute(
711
- lambda o: o.parent.controller if o.parent else Controller.objects.order_by("?").first()
711
+ lambda o: o.parent.controller if o.parent else factory.random.randgen.choice(Controller.objects.all())
712
712
  )
713
713
  weight = factory.Faker("pyint", min_value=1, max_value=1000)
@@ -1854,12 +1854,14 @@ class SoftwareVersionTestCase(ModelTestCases.BaseModelTestCase):
1854
1854
 
1855
1855
  # Only return the device types with a direct m2m relationship to the version's software image files
1856
1856
  device_type = DeviceType.objects.filter(software_image_files__isnull=False).first()
1857
+ self.assertIsNotNone(device_type)
1857
1858
  self.assertQuerysetEqualAndNotEmpty(
1858
1859
  qs.get_for_object(device_type), qs.filter(software_image_files__device_types=device_type)
1859
1860
  )
1860
1861
 
1861
1862
  # Only return the software version set on the device's software_version foreign key
1862
1863
  device = Device.objects.filter(software_version__isnull=False).first()
1864
+ self.assertIsNotNone(device)
1863
1865
  self.assertQuerysetEqualAndNotEmpty(qs.get_for_object(device), [device.software_version])
1864
1866
 
1865
1867
  # Only return the software version set on the inventory item's software_version foreign key
@@ -136,7 +136,7 @@ def get_repo_from_url_to_path_and_from_branch(repository_record):
136
136
 
137
137
  def ensure_git_repository(repository_record, logger=None, head=None): # pylint: disable=redefined-outer-name
138
138
  """Ensure that the given Git repo is present, up-to-date, and has the correct branch selected.
139
- Note that this function may be called independently of the `GitRepositoryiSync` job,
139
+ Note that this function may be called independently of the `GitRepositorySync` job,
140
140
  such as to ensure that different Nautobot instances and/or worker instances all have a local copy of the same HEAD.
141
141
  Args:
142
142
  repository_record (GitRepository): Repository to ensure the state of.
@@ -224,126 +224,127 @@ def refresh_git_config_contexts(repository_record, job_result, delete=False):
224
224
  def update_git_config_contexts(repository_record, job_result):
225
225
  """Refresh any config contexts provided by this Git repository."""
226
226
  config_context_path = os.path.join(repository_record.filesystem_path, "config_contexts")
227
- if not os.path.isdir(config_context_path):
228
- return
229
-
230
227
  managed_config_contexts = set()
231
228
  managed_local_config_contexts = defaultdict(set)
232
229
 
233
- # First, handle the "flat file" case - data files in the root config_context_path,
234
- # whose metadata is expressed purely within the contents of the file:
235
- for file_name in os.listdir(config_context_path):
236
- if not os.path.isfile(os.path.join(config_context_path, file_name)):
237
- continue
238
- msg = f"Loading config context from `{file_name}`"
239
- logger.info(msg)
240
- job_result.log(msg, grouping="config contexts")
241
- try:
242
- with open(os.path.join(config_context_path, file_name), "r") as fd:
243
- # The data file can be either JSON or YAML; since YAML is a superset of JSON, we can load it regardless
244
- context_data = yaml.safe_load(fd)
245
-
246
- # A file can contain one config context dict or a list thereof
247
- if isinstance(context_data, dict):
248
- context_name = import_config_context(context_data, repository_record, job_result)
249
- managed_config_contexts.add(context_name)
250
- elif isinstance(context_data, list):
251
- for context_data_entry in context_data:
252
- context_name = import_config_context(context_data_entry, repository_record, job_result)
253
- managed_config_contexts.add(context_name)
254
- else:
255
- raise RuntimeError("data must be a dict or list of dicts")
256
-
257
- except Exception as exc:
258
- msg = f"Error in loading config context data from `{file_name}`: {exc}"
259
- logger.error(msg)
260
- job_result.log(msg, level_choice=LogLevelChoices.LOG_ERROR, grouping="config contexts")
261
-
262
- # Next, handle the "filter/name" directory structure case - files in <filter_type>/<name>.(json|yaml)
263
- for filter_type in (
264
- "locations",
265
- "device_types",
266
- "roles",
267
- "platforms",
268
- "cluster_groups",
269
- "clusters",
270
- "tenant_groups",
271
- "tenants",
272
- "tags",
273
- "dynamic_groups",
274
- "device_redundancy_groups",
275
- ):
276
- if os.path.isdir(os.path.join(repository_record.filesystem_path, filter_type)):
277
- msg = (
278
- f'Found "{filter_type}" directory in the repository root. If this is meant to contain config contexts, '
279
- "it should be moved into a `config_contexts/` subdirectory."
280
- )
281
- logger.warning(msg)
282
- job_result.log(msg, level_choice=LogLevelChoices.LOG_WARNING, grouping="config contexts")
283
-
284
- dir_path = os.path.join(config_context_path, filter_type)
285
- if not os.path.isdir(dir_path):
286
- continue
287
-
288
- for file_name in os.listdir(dir_path):
289
- name = os.path.splitext(file_name)[0]
290
- msg = f'Loading config context, filter `{filter_type} = [name: "{name}"]`, from `{filter_type}/{file_name}`'
230
+ if os.path.isdir(config_context_path):
231
+ # First, handle the "flat file" case - data files in the root config_context_path,
232
+ # whose metadata is expressed purely within the contents of the file:
233
+ for file_name in os.listdir(config_context_path):
234
+ if not os.path.isfile(os.path.join(config_context_path, file_name)):
235
+ continue
236
+ msg = f"Loading config context from `{file_name}`"
291
237
  logger.info(msg)
292
238
  job_result.log(msg, grouping="config contexts")
293
239
  try:
294
- with open(os.path.join(dir_path, file_name), "r") as fd:
295
- # Data file can be either JSON or YAML; since YAML is a superset of JSON, we can load it regardless
240
+ with open(os.path.join(config_context_path, file_name), "r") as fd:
241
+ # The data file can be either JSON or YAML; since YAML is a superset of JSON, we load it regardless
296
242
  context_data = yaml.safe_load(fd)
297
243
 
298
- # Unlike the above case, these files always contain just a single config context record
299
-
300
- # Add the implied filter to the context metadata
301
- if filter_type == "device_types":
302
- context_data.setdefault("_metadata", {}).setdefault(filter_type, []).append({"model": name})
244
+ # A file can contain one config context dict or a list thereof
245
+ if isinstance(context_data, dict):
246
+ context_name = import_config_context(context_data, repository_record, job_result)
247
+ managed_config_contexts.add(context_name)
248
+ elif isinstance(context_data, list):
249
+ for context_data_entry in context_data:
250
+ context_name = import_config_context(context_data_entry, repository_record, job_result)
251
+ managed_config_contexts.add(context_name)
303
252
  else:
304
- context_data.setdefault("_metadata", {}).setdefault(filter_type, []).append({"name": name})
253
+ raise RuntimeError("data must be a dict or list of dicts")
305
254
 
306
- context_name = import_config_context(context_data, repository_record, job_result)
307
- managed_config_contexts.add(context_name)
308
255
  except Exception as exc:
309
256
  msg = f"Error in loading config context data from `{file_name}`: {exc}"
310
257
  logger.error(msg)
311
258
  job_result.log(msg, level_choice=LogLevelChoices.LOG_ERROR, grouping="config contexts")
312
259
 
313
- # Finally, handle device- and virtual-machine-specific "local" context in (devices|virtual_machines)/<name>.(json|yaml)
314
- for local_type in ("devices", "virtual_machines"):
315
- if os.path.isdir(os.path.join(repository_record.filesystem_path, local_type)):
316
- msg = (
317
- f'Found "{local_type}" directory in the repository root. If this is meant to contain config contexts, '
318
- "it should be moved into a `config_contexts/` subdirectory."
319
- )
320
- logger.warning(msg)
321
- job_result.log(msg, level_choice=LogLevelChoices.LOG_WARNING, grouping="config contexts")
260
+ # Next, handle the "filter/name" directory structure case - files in <filter_type>/<name>.(json|yaml)
261
+ for filter_type in (
262
+ "locations",
263
+ "device_types",
264
+ "roles",
265
+ "platforms",
266
+ "cluster_groups",
267
+ "clusters",
268
+ "tenant_groups",
269
+ "tenants",
270
+ "tags",
271
+ "dynamic_groups",
272
+ "device_redundancy_groups",
273
+ ):
274
+ if os.path.isdir(os.path.join(repository_record.filesystem_path, filter_type)):
275
+ msg = (
276
+ f'Found "{filter_type}" directory in the repository root. If this is meant to contain config contexts, '
277
+ "it should be moved into a `config_contexts/` subdirectory."
278
+ )
279
+ logger.warning(msg)
280
+ job_result.log(msg, level_choice=LogLevelChoices.LOG_WARNING, grouping="config contexts")
322
281
 
323
- dir_path = os.path.join(config_context_path, local_type)
324
- if not os.path.isdir(dir_path):
325
- continue
282
+ dir_path = os.path.join(config_context_path, filter_type)
283
+ if not os.path.isdir(dir_path):
284
+ continue
326
285
 
327
- for file_name in os.listdir(dir_path):
328
- device_name = os.path.splitext(file_name)[0]
329
- msg = f"Loading local config context for `{device_name}` from `{local_type}/{file_name}`"
330
- logger.info(msg)
331
- job_result.log(msg, grouping="local config contexts")
332
- try:
333
- with open(os.path.join(dir_path, file_name), "r") as fd:
334
- context_data = yaml.safe_load(fd)
286
+ for file_name in os.listdir(dir_path):
287
+ name = os.path.splitext(file_name)[0]
288
+ msg = (
289
+ f'Loading config context, filter `{filter_type} = [name: "{name}"]`, '
290
+ f"from `{filter_type}/{file_name}`"
291
+ )
292
+ logger.info(msg)
293
+ job_result.log(msg, grouping="config contexts")
294
+ try:
295
+ with open(os.path.join(dir_path, file_name), "r") as fd:
296
+ # Data file can be either JSON or YAML; since YAML is a superset of JSON, we load it regardless
297
+ context_data = yaml.safe_load(fd)
298
+
299
+ # Unlike the above case, these files always contain just a single config context record
300
+
301
+ # Add the implied filter to the context metadata
302
+ if filter_type == "device_types":
303
+ context_data.setdefault("_metadata", {}).setdefault(filter_type, []).append({"model": name})
304
+ else:
305
+ context_data.setdefault("_metadata", {}).setdefault(filter_type, []).append({"name": name})
335
306
 
336
- import_local_config_context(
337
- local_type,
338
- device_name,
339
- context_data,
340
- repository_record,
307
+ context_name = import_config_context(context_data, repository_record, job_result)
308
+ managed_config_contexts.add(context_name)
309
+ except Exception as exc:
310
+ msg = f"Error in loading config context data from `{file_name}`: {exc}"
311
+ logger.error(msg)
312
+ job_result.log(msg, level_choice=LogLevelChoices.LOG_ERROR, grouping="config contexts")
313
+
314
+ # Finally, handle device- and VM-specific "local" context in (devices|virtual_machines)/<name>.(json|yaml)
315
+ for local_type in ("devices", "virtual_machines"):
316
+ if os.path.isdir(os.path.join(repository_record.filesystem_path, local_type)):
317
+ msg = (
318
+ f'Found "{local_type}" directory in the repository root. If this is meant to contain '
319
+ "config contexts, it should be moved into a `config_contexts/` subdirectory."
341
320
  )
342
- managed_local_config_contexts[local_type].add(device_name)
343
- except Exception as exc:
344
- msg = f"Error in loading local config context from `{local_type}/{file_name}`: {exc}"
345
- logger.error(msg)
346
- job_result.log(msg, level_choice=LogLevelChoices.LOG_ERROR, grouping="local config contexts")
321
+ logger.warning(msg)
322
+ job_result.log(msg, level_choice=LogLevelChoices.LOG_WARNING, grouping="config contexts")
323
+
324
+ dir_path = os.path.join(config_context_path, local_type)
325
+ if not os.path.isdir(dir_path):
326
+ continue
327
+
328
+ for file_name in os.listdir(dir_path):
329
+ device_name = os.path.splitext(file_name)[0]
330
+ msg = f"Loading local config context for `{device_name}` from `{local_type}/{file_name}`"
331
+ logger.info(msg)
332
+ job_result.log(msg, grouping="local config contexts")
333
+ try:
334
+ with open(os.path.join(dir_path, file_name), "r") as fd:
335
+ context_data = yaml.safe_load(fd)
336
+
337
+ import_local_config_context(
338
+ local_type,
339
+ device_name,
340
+ context_data,
341
+ repository_record,
342
+ )
343
+ managed_local_config_contexts[local_type].add(device_name)
344
+ except Exception as exc:
345
+ msg = f"Error in loading local config context from `{local_type}/{file_name}`: {exc}"
346
+ logger.error(msg)
347
+ job_result.log(msg, level_choice=LogLevelChoices.LOG_ERROR, grouping="local config contexts")
347
348
 
348
349
  # Delete any prior contexts that are owned by this repository but were not created/updated above
349
350
  delete_git_config_contexts(
@@ -599,39 +600,38 @@ def refresh_git_config_context_schemas(repository_record, job_result, delete=Fal
599
600
  def update_git_config_context_schemas(repository_record, job_result):
600
601
  """Refresh any config context schemas provided by this Git repository."""
601
602
  config_context_schema_path = os.path.join(repository_record.filesystem_path, "config_context_schemas")
602
- if not os.path.isdir(config_context_schema_path):
603
- return
604
603
 
605
604
  managed_config_context_schemas = set()
606
605
 
607
- for file_name in os.listdir(config_context_schema_path):
608
- if not os.path.isfile(os.path.join(config_context_schema_path, file_name)):
609
- continue
610
- msg = (f"Loading config context schema from `{file_name}`",)
611
- logger.info(msg)
612
- job_result.log(msg, grouping="config context schemas")
613
- try:
614
- with open(os.path.join(config_context_schema_path, file_name), "r") as fd:
615
- # The data file can be either JSON or YAML; since YAML is a superset of JSON, we can load it regardless
616
- context_schema_data = yaml.safe_load(fd)
617
-
618
- # A file can contain one config context dict or a list thereof
619
- if isinstance(context_schema_data, dict):
620
- context_name = import_config_context_schema(context_schema_data, repository_record, job_result)
621
- managed_config_context_schemas.add(context_name)
622
- elif isinstance(context_schema_data, list):
623
- for context_schema in context_schema_data:
624
- if isinstance(context_schema, dict):
625
- context_name = import_config_context_schema(context_schema, repository_record, job_result)
626
- managed_config_context_schemas.add(context_name)
627
- else:
628
- raise RuntimeError("each item in list data must be a dict")
629
- else:
630
- raise RuntimeError("data must be a dict or a list of dicts")
631
- except Exception as exc:
632
- msg = f"Error in loading config context schema data from `{file_name}`: {exc}"
633
- logger.error(msg)
634
- job_result.log(msg, level_choice=LogLevelChoices.LOG_ERROR, grouping="config context schemas")
606
+ if os.path.isdir(config_context_schema_path):
607
+ for file_name in os.listdir(config_context_schema_path):
608
+ if not os.path.isfile(os.path.join(config_context_schema_path, file_name)):
609
+ continue
610
+ msg = (f"Loading config context schema from `{file_name}`",)
611
+ logger.info(msg)
612
+ job_result.log(msg, grouping="config context schemas")
613
+ try:
614
+ with open(os.path.join(config_context_schema_path, file_name), "r") as fd:
615
+ # The data file can be either JSON or YAML; since YAML is a superset of JSON, we load it regardless
616
+ context_schema_data = yaml.safe_load(fd)
617
+
618
+ # A file can contain one config context dict or a list thereof
619
+ if isinstance(context_schema_data, dict):
620
+ context_name = import_config_context_schema(context_schema_data, repository_record, job_result)
621
+ managed_config_context_schemas.add(context_name)
622
+ elif isinstance(context_schema_data, list):
623
+ for context_schema in context_schema_data:
624
+ if isinstance(context_schema, dict):
625
+ context_name = import_config_context_schema(context_schema, repository_record, job_result)
626
+ managed_config_context_schemas.add(context_name)
627
+ else:
628
+ raise RuntimeError("each item in list data must be a dict")
629
+ else:
630
+ raise RuntimeError("data must be a dict or a list of dicts")
631
+ except Exception as exc:
632
+ msg = f"Error in loading config context schema data from `{file_name}`: {exc}"
633
+ logger.error(msg)
634
+ job_result.log(msg, level_choice=LogLevelChoices.LOG_ERROR, grouping="config context schemas")
635
635
 
636
636
  # Delete any prior contexts that are owned by this repository but were not created/updated above
637
637
  delete_git_config_context_schemas(
@@ -835,12 +835,10 @@ def update_git_export_templates(repository_record, job_result):
835
835
  job_result.log(msg, level_choice=LogLevelChoices.LOG_WARNING, grouping="export templates")
836
836
 
837
837
  export_template_path = os.path.join(repository_record.filesystem_path, "export_templates")
838
- if not os.path.isdir(export_template_path):
839
- return
838
+ managed_export_templates = {}
840
839
 
841
840
  git_repository_content_type = ContentType.objects.get_for_model(GitRepository)
842
841
 
843
- managed_export_templates = {}
844
842
  for model_content_type, file_path in files_from_contenttype_directories(
845
843
  export_template_path, job_result, "export templates"
846
844
  ):
@@ -15,6 +15,9 @@ def files_from_contenttype_directories(base_path, job_result, log_grouping):
15
15
  Returns:
16
16
  (Tuple[ContentType, file_path]): A tuple of the ContentType and the file path.
17
17
  """
18
+ if not os.path.isdir(base_path):
19
+ return
20
+
18
21
  for app_label in os.listdir(base_path):
19
22
  app_label_path = os.path.join(base_path, app_label)
20
23
  if not os.path.isdir(app_label_path):
@@ -902,6 +902,7 @@ class JobButtonFilterSet(BaseFilterSet):
902
902
  fields = (
903
903
  "content_types",
904
904
  "name",
905
+ "enabled",
905
906
  "text",
906
907
  "job",
907
908
  "weight",