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.
- nautobot/apps/api.py +2 -0
- nautobot/apps/models.py +2 -0
- nautobot/core/api/fields.py +13 -0
- nautobot/core/api/mixins.py +6 -1
- nautobot/core/api/schema.py +3 -1
- nautobot/core/api/serializers.py +7 -1
- nautobot/core/celery/__init__.py +1 -1
- nautobot/core/management/commands/generate_test_data.py +128 -158
- nautobot/core/models/fields.py +15 -0
- nautobot/core/tests/runner.py +10 -0
- nautobot/core/tests/test_utils.py +48 -1
- nautobot/core/utils/git.py +121 -49
- nautobot/core/utils/module_loading.py +10 -2
- nautobot/dcim/factory.py +1 -1
- nautobot/dcim/tests/test_models.py +2 -0
- nautobot/extras/datasources/git.py +133 -135
- nautobot/extras/datasources/utils.py +3 -0
- nautobot/extras/filters/__init__.py +1 -0
- nautobot/extras/forms/forms.py +16 -3
- nautobot/extras/jobs.py +9 -1
- nautobot/extras/migrations/0107_laxurlfield.py +28 -0
- nautobot/extras/migrations/0108_jobbutton_enabled.py +17 -0
- nautobot/extras/models/datasources.py +6 -4
- nautobot/extras/models/groups.py +9 -2
- nautobot/extras/models/jobs.py +30 -0
- nautobot/extras/models/models.py +2 -4
- nautobot/extras/tables.py +3 -0
- nautobot/extras/templates/extras/jobbutton_retrieve.html +6 -2
- nautobot/extras/templatetags/job_buttons.py +2 -2
- nautobot/extras/tests/git_data/01-valid-files/__init__.py +0 -0
- nautobot/extras/tests/git_data/01-valid-files/config_context_schemas/schema-1.yaml +18 -0
- nautobot/extras/tests/git_data/01-valid-files/config_contexts/context.yaml +12 -0
- nautobot/extras/tests/git_data/01-valid-files/config_contexts/devices/test-device.json +3 -0
- nautobot/extras/tests/git_data/01-valid-files/config_contexts/locations/Test Location.json +7 -0
- nautobot/extras/tests/git_data/01-valid-files/export_templates/dcim/device/template.j2 +3 -0
- nautobot/extras/tests/git_data/01-valid-files/export_templates/dcim/device/template2.html +4 -0
- nautobot/extras/tests/git_data/01-valid-files/export_templates/ipam/vlan/template.j2 +3 -0
- nautobot/extras/tests/git_data/01-valid-files/jobs/__init__.py +5 -0
- nautobot/extras/tests/git_data/01-valid-files/jobs/my_job.py +16 -0
- nautobot/extras/tests/git_data/02-invalid-files/__init__.py +0 -0
- nautobot/extras/tests/git_data/02-invalid-files/config_context_schemas/badschema1.json +2 -0
- nautobot/extras/tests/git_data/02-invalid-files/config_context_schemas/badschema2.json +1 -0
- nautobot/extras/tests/git_data/02-invalid-files/config_contexts/badcontext1.json +2 -0
- nautobot/extras/tests/git_data/02-invalid-files/config_contexts/badcontext2.json +1 -0
- nautobot/extras/tests/git_data/02-invalid-files/config_contexts/badcontext3.json +3 -0
- nautobot/extras/tests/git_data/02-invalid-files/config_contexts/devices/nosuchdevice.json +1 -0
- nautobot/extras/tests/git_data/02-invalid-files/dcim/template.j2 +0 -0
- nautobot/extras/tests/git_data/02-invalid-files/devices/template.j2 +0 -0
- nautobot/extras/tests/git_data/02-invalid-files/export_templates/dcim/nosuchmodel/template.j2 +3 -0
- nautobot/extras/tests/git_data/02-invalid-files/export_templates/nosuchapp/device/template.j2 +3 -0
- nautobot/extras/tests/git_data/02-invalid-files/jobs/__init__.py +2 -0
- nautobot/extras/tests/git_data/02-invalid-files/jobs/importerror.py +1 -0
- nautobot/extras/tests/git_data/02-invalid-files/jobs/syntaxerror.py +1 -0
- nautobot/extras/tests/git_helper.py +76 -0
- nautobot/extras/tests/test_api.py +28 -11
- nautobot/extras/tests/test_datasources.py +94 -276
- nautobot/extras/tests/test_dynamicgroups.py +8 -1
- nautobot/extras/tests/test_models.py +8 -3
- nautobot/extras/tests/test_views.py +21 -7
- nautobot/extras/views.py +1 -1
- nautobot/ipam/api/serializers.py +46 -16
- nautobot/ipam/api/views.py +29 -16
- nautobot/ipam/filters.py +9 -1
- nautobot/ipam/forms.py +8 -0
- nautobot/ipam/tables.py +1 -1
- nautobot/ipam/tests/test_api.py +15 -20
- nautobot/ipam/tests/test_filters.py +15 -0
- nautobot/project-static/docs/404.html +83 -8
- nautobot/project-static/docs/apps/index.html +96 -10
- nautobot/project-static/docs/apps/nautobot-apps.html +96 -10
- nautobot/project-static/docs/assets/app-icons/icon-CapacityMetrics.svg +1 -0
- nautobot/project-static/docs/assets/app-icons/icon-CircuitMaintenance.png +0 -0
- nautobot/project-static/docs/assets/javascripts/{bundle.ebd0bdb7.min.js → bundle.fe8b6f2b.min.js} +4 -4
- nautobot/project-static/docs/assets/javascripts/{bundle.ebd0bdb7.min.js.map → bundle.fe8b6f2b.min.js.map} +3 -3
- nautobot/project-static/docs/assets/javascripts/glightbox.min.js +1 -0
- nautobot/project-static/docs/assets/stylesheets/glightbox.min.css +1 -0
- nautobot/project-static/docs/code-reference/nautobot/apps/__init__.html +96 -10
- nautobot/project-static/docs/code-reference/nautobot/apps/admin.html +96 -10
- nautobot/project-static/docs/code-reference/nautobot/apps/api.html +96 -10
- nautobot/project-static/docs/code-reference/nautobot/apps/change_logging.html +96 -10
- nautobot/project-static/docs/code-reference/nautobot/apps/choices.html +96 -10
- nautobot/project-static/docs/code-reference/nautobot/apps/config.html +96 -10
- nautobot/project-static/docs/code-reference/nautobot/apps/constants.html +96 -10
- nautobot/project-static/docs/code-reference/nautobot/apps/datasources.html +96 -10
- nautobot/project-static/docs/code-reference/nautobot/apps/exceptions.html +96 -10
- nautobot/project-static/docs/code-reference/nautobot/apps/factory.html +96 -10
- nautobot/project-static/docs/code-reference/nautobot/apps/filters.html +96 -10
- nautobot/project-static/docs/code-reference/nautobot/apps/forms.html +96 -10
- nautobot/project-static/docs/code-reference/nautobot/apps/graphql.html +96 -10
- nautobot/project-static/docs/code-reference/nautobot/apps/jobs.html +96 -10
- nautobot/project-static/docs/code-reference/nautobot/apps/models.html +156 -12
- nautobot/project-static/docs/code-reference/nautobot/apps/querysets.html +96 -10
- nautobot/project-static/docs/code-reference/nautobot/apps/secrets.html +96 -10
- nautobot/project-static/docs/code-reference/nautobot/apps/tables.html +96 -10
- nautobot/project-static/docs/code-reference/nautobot/apps/testing.html +106 -11
- nautobot/project-static/docs/code-reference/nautobot/apps/ui.html +96 -10
- nautobot/project-static/docs/code-reference/nautobot/apps/urls.html +96 -10
- nautobot/project-static/docs/code-reference/nautobot/apps/utils.html +143 -11
- nautobot/project-static/docs/code-reference/nautobot/apps/views.html +96 -10
- nautobot/project-static/docs/development/apps/api/configuration-view.html +96 -10
- nautobot/project-static/docs/development/apps/api/database-backend-config.html +96 -10
- nautobot/project-static/docs/development/apps/api/models/django-admin.html +96 -10
- nautobot/project-static/docs/development/apps/api/models/global-search.html +96 -10
- nautobot/project-static/docs/development/apps/api/models/graphql.html +96 -10
- nautobot/project-static/docs/development/apps/api/models/index.html +96 -10
- nautobot/project-static/docs/development/apps/api/nautobot-app-config.html +96 -10
- nautobot/project-static/docs/development/apps/api/platform-features/custom-validators.html +96 -10
- nautobot/project-static/docs/development/apps/api/platform-features/filter-extensions.html +96 -10
- nautobot/project-static/docs/development/apps/api/platform-features/git-repository-content.html +96 -10
- nautobot/project-static/docs/development/apps/api/platform-features/index.html +96 -10
- nautobot/project-static/docs/development/apps/api/platform-features/jinja2-filters.html +96 -10
- nautobot/project-static/docs/development/apps/api/platform-features/jobs.html +96 -10
- nautobot/project-static/docs/development/apps/api/platform-features/populating-extensibility-features.html +96 -10
- nautobot/project-static/docs/development/apps/api/platform-features/secrets-providers.html +96 -10
- nautobot/project-static/docs/development/apps/api/platform-features/uniquely-identify-objects.html +96 -10
- nautobot/project-static/docs/development/apps/api/prometheus.html +96 -10
- nautobot/project-static/docs/development/apps/api/setup.html +96 -10
- nautobot/project-static/docs/development/apps/api/testing.html +96 -10
- nautobot/project-static/docs/development/apps/api/ui-extensions/banners.html +96 -10
- nautobot/project-static/docs/development/apps/api/ui-extensions/home-page.html +96 -10
- nautobot/project-static/docs/development/apps/api/ui-extensions/index.html +96 -10
- nautobot/project-static/docs/development/apps/api/ui-extensions/navigation.html +96 -10
- nautobot/project-static/docs/development/apps/api/ui-extensions/object-views.html +96 -10
- nautobot/project-static/docs/development/apps/api/views/base-template.html +96 -10
- nautobot/project-static/docs/development/apps/api/views/core-view-overrides.html +96 -10
- nautobot/project-static/docs/development/apps/api/views/django-generic-views.html +96 -10
- nautobot/project-static/docs/development/apps/api/views/help-documentation.html +96 -10
- nautobot/project-static/docs/development/apps/api/views/index.html +96 -10
- nautobot/project-static/docs/development/apps/api/views/nautobot-generic-views.html +96 -10
- nautobot/project-static/docs/development/apps/api/views/nautobotuiviewset.html +96 -10
- nautobot/project-static/docs/development/apps/api/views/nautobotuiviewsetrouter.html +96 -10
- nautobot/project-static/docs/development/apps/api/views/notes.html +96 -10
- nautobot/project-static/docs/development/apps/api/views/rest-api.html +96 -10
- nautobot/project-static/docs/development/apps/api/views/urls.html +96 -10
- nautobot/project-static/docs/development/apps/index.html +96 -10
- nautobot/project-static/docs/development/apps/migration/code-updates.html +97 -11
- nautobot/project-static/docs/development/apps/migration/dependency-updates.html +96 -10
- nautobot/project-static/docs/development/apps/migration/from-v1.html +96 -10
- nautobot/project-static/docs/development/apps/migration/model-updates/dcim.html +96 -10
- nautobot/project-static/docs/development/apps/migration/model-updates/extras.html +96 -10
- nautobot/project-static/docs/development/apps/migration/model-updates/global.html +96 -10
- nautobot/project-static/docs/development/apps/migration/model-updates/ipam.html +96 -10
- nautobot/project-static/docs/development/apps/porting-from-netbox.html +96 -10
- nautobot/project-static/docs/development/core/application-registry.html +96 -10
- nautobot/project-static/docs/development/core/best-practices.html +96 -10
- nautobot/project-static/docs/development/core/bootstrap-ui.html +96 -10
- nautobot/project-static/docs/development/core/caching.html +96 -10
- nautobot/project-static/docs/development/core/controllers.html +96 -10
- nautobot/project-static/docs/development/core/docker-compose-advanced-use-cases.html +96 -10
- nautobot/project-static/docs/development/core/generic-views.html +96 -10
- nautobot/project-static/docs/development/core/getting-started.html +98 -13
- nautobot/project-static/docs/development/core/homepage.html +96 -10
- nautobot/project-static/docs/development/core/index.html +96 -10
- nautobot/project-static/docs/development/core/model-checklist.html +96 -10
- nautobot/project-static/docs/development/core/model-features.html +96 -10
- nautobot/project-static/docs/development/core/natural-keys.html +96 -10
- nautobot/project-static/docs/development/core/navigation-menu.html +96 -10
- nautobot/project-static/docs/development/core/release-checklist.html +96 -10
- nautobot/project-static/docs/development/core/role-internals.html +96 -10
- nautobot/project-static/docs/development/core/settings.html +96 -10
- nautobot/project-static/docs/development/core/style-guide.html +96 -10
- nautobot/project-static/docs/development/core/templates.html +96 -10
- nautobot/project-static/docs/development/core/testing.html +109 -11
- nautobot/project-static/docs/development/core/user-preferences.html +96 -10
- nautobot/project-static/docs/development/index.html +96 -10
- nautobot/project-static/docs/development/jobs/index.html +96 -10
- nautobot/project-static/docs/development/jobs/migration/from-v1.html +96 -10
- nautobot/project-static/docs/index.html +13 -8362
- nautobot/project-static/docs/objects.inv +0 -0
- nautobot/project-static/docs/overview/application_stack.html +8229 -0
- nautobot/project-static/docs/overview/design_philosophy.html +8158 -0
- nautobot/project-static/docs/overview/index.html +8230 -0
- nautobot/project-static/docs/release-notes/index.html +96 -10
- nautobot/project-static/docs/release-notes/version-1.0.html +96 -10
- nautobot/project-static/docs/release-notes/version-1.1.html +96 -10
- nautobot/project-static/docs/release-notes/version-1.2.html +96 -10
- nautobot/project-static/docs/release-notes/version-1.3.html +96 -10
- nautobot/project-static/docs/release-notes/version-1.4.html +96 -10
- nautobot/project-static/docs/release-notes/version-1.5.html +96 -10
- nautobot/project-static/docs/release-notes/version-1.6.html +96 -10
- nautobot/project-static/docs/release-notes/version-2.0.html +96 -10
- nautobot/project-static/docs/release-notes/version-2.1.html +96 -10
- nautobot/project-static/docs/release-notes/version-2.2.html +516 -131
- nautobot/project-static/docs/requirements.txt +2 -1
- nautobot/project-static/docs/search/search_index.json +1 -1
- nautobot/project-static/docs/sitemap.xml +268 -258
- nautobot/project-static/docs/sitemap.xml.gz +0 -0
- nautobot/project-static/docs/user-guide/administration/configuration/authentication/ldap.html +96 -10
- nautobot/project-static/docs/user-guide/administration/configuration/authentication/remote.html +96 -10
- nautobot/project-static/docs/user-guide/administration/configuration/authentication/sso.html +96 -10
- nautobot/project-static/docs/user-guide/administration/configuration/index.html +96 -10
- nautobot/project-static/docs/user-guide/administration/configuration/optional-settings.html +96 -10
- nautobot/project-static/docs/user-guide/administration/configuration/required-settings.html +96 -10
- nautobot/project-static/docs/user-guide/administration/configuration/time-zones.html +96 -10
- nautobot/project-static/docs/user-guide/administration/guides/caching.html +96 -10
- nautobot/project-static/docs/user-guide/administration/guides/celery-queues.html +96 -10
- nautobot/project-static/docs/user-guide/administration/guides/healthcheck.html +96 -10
- nautobot/project-static/docs/user-guide/administration/guides/permissions.html +96 -10
- nautobot/project-static/docs/user-guide/administration/guides/prometheus-metrics.html +96 -10
- nautobot/project-static/docs/user-guide/administration/guides/replicating-nautobot.html +96 -10
- nautobot/project-static/docs/user-guide/administration/guides/request-profiling.html +96 -10
- nautobot/project-static/docs/user-guide/administration/guides/s3-django-storage.html +96 -10
- nautobot/project-static/docs/user-guide/administration/installation/app-install.html +96 -10
- nautobot/project-static/docs/user-guide/administration/installation/external-authentication.html +96 -10
- nautobot/project-static/docs/user-guide/administration/installation/http-server.html +96 -10
- nautobot/project-static/docs/user-guide/administration/installation/index.html +96 -10
- nautobot/project-static/docs/user-guide/administration/installation/install_system.html +96 -10
- nautobot/project-static/docs/user-guide/administration/installation/nautobot.html +96 -10
- nautobot/project-static/docs/user-guide/administration/installation/services.html +96 -10
- nautobot/project-static/docs/user-guide/administration/installation-extras/docker.html +96 -10
- nautobot/project-static/docs/user-guide/administration/installation-extras/health-checks.html +96 -10
- nautobot/project-static/docs/user-guide/administration/installation-extras/selinux-troubleshooting.html +96 -10
- nautobot/project-static/docs/user-guide/administration/migration/migrating-from-netbox.html +96 -10
- nautobot/project-static/docs/user-guide/administration/migration/migrating-from-postgresql.html +96 -10
- nautobot/project-static/docs/user-guide/administration/tools/nautobot-server.html +96 -10
- nautobot/project-static/docs/user-guide/administration/tools/nautobot-shell.html +96 -10
- nautobot/project-static/docs/user-guide/administration/upgrading/database-backup.html +96 -10
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/after-you-upgrade.html +96 -10
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/before-you-upgrade.html +96 -10
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/for-developers.html +96 -10
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/index.html +96 -10
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/whats-changed.html +96 -10
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/region-and-site-data-migration-guide.html +96 -10
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/tables/v2-code-location-changes.yaml +1 -1
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/upgrading-from-nautobot-v1.html +97 -11
- nautobot/project-static/docs/user-guide/administration/upgrading/upgrading.html +96 -10
- nautobot/project-static/docs/user-guide/core-data-model/circuits/circuit.html +96 -10
- nautobot/project-static/docs/user-guide/core-data-model/circuits/circuittermination.html +96 -10
- nautobot/project-static/docs/user-guide/core-data-model/circuits/circuittype.html +96 -10
- nautobot/project-static/docs/user-guide/core-data-model/circuits/provider.html +96 -10
- nautobot/project-static/docs/user-guide/core-data-model/circuits/providernetwork.html +96 -10
- nautobot/project-static/docs/user-guide/core-data-model/dcim/cable.html +96 -10
- nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleport.html +96 -10
- nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleporttemplate.html +96 -10
- nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleserverport.html +96 -10
- nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleserverporttemplate.html +96 -10
- nautobot/project-static/docs/user-guide/core-data-model/dcim/controller.html +96 -10
- nautobot/project-static/docs/user-guide/core-data-model/dcim/controllermanageddevicegroup.html +96 -10
- nautobot/project-static/docs/user-guide/core-data-model/dcim/device.html +96 -10
- nautobot/project-static/docs/user-guide/core-data-model/dcim/devicebay.html +96 -10
- nautobot/project-static/docs/user-guide/core-data-model/dcim/devicebaytemplate.html +96 -10
- nautobot/project-static/docs/user-guide/core-data-model/dcim/devicefamily.html +96 -10
- nautobot/project-static/docs/user-guide/core-data-model/dcim/deviceredundancygroup.html +96 -10
- nautobot/project-static/docs/user-guide/core-data-model/dcim/devicetype.html +96 -10
- nautobot/project-static/docs/user-guide/core-data-model/dcim/frontport.html +96 -10
- nautobot/project-static/docs/user-guide/core-data-model/dcim/frontporttemplate.html +96 -10
- nautobot/project-static/docs/user-guide/core-data-model/dcim/interface.html +96 -10
- nautobot/project-static/docs/user-guide/core-data-model/dcim/interfaceredundancygroup.html +96 -10
- nautobot/project-static/docs/user-guide/core-data-model/dcim/interfacetemplate.html +96 -10
- nautobot/project-static/docs/user-guide/core-data-model/dcim/inventoryitem.html +96 -10
- nautobot/project-static/docs/user-guide/core-data-model/dcim/location.html +96 -10
- nautobot/project-static/docs/user-guide/core-data-model/dcim/locationtype.html +96 -10
- nautobot/project-static/docs/user-guide/core-data-model/dcim/manufacturer.html +96 -10
- nautobot/project-static/docs/user-guide/core-data-model/dcim/platform.html +96 -10
- nautobot/project-static/docs/user-guide/core-data-model/dcim/powerfeed.html +96 -10
- nautobot/project-static/docs/user-guide/core-data-model/dcim/poweroutlet.html +96 -10
- nautobot/project-static/docs/user-guide/core-data-model/dcim/poweroutlettemplate.html +96 -10
- nautobot/project-static/docs/user-guide/core-data-model/dcim/powerpanel.html +96 -10
- nautobot/project-static/docs/user-guide/core-data-model/dcim/powerport.html +96 -10
- nautobot/project-static/docs/user-guide/core-data-model/dcim/powerporttemplate.html +96 -10
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rack.html +96 -10
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rackgroup.html +96 -10
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rackreservation.html +96 -10
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rearport.html +96 -10
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rearporttemplate.html +96 -10
- nautobot/project-static/docs/user-guide/core-data-model/dcim/softwareimagefile.html +96 -10
- nautobot/project-static/docs/user-guide/core-data-model/dcim/softwareversion.html +96 -10
- nautobot/project-static/docs/user-guide/core-data-model/dcim/virtualchassis.html +96 -10
- nautobot/project-static/docs/user-guide/core-data-model/extras/configcontext.html +96 -10
- nautobot/project-static/docs/user-guide/core-data-model/extras/configcontextschema.html +96 -10
- nautobot/project-static/docs/user-guide/core-data-model/extras/contact.html +96 -10
- nautobot/project-static/docs/user-guide/core-data-model/extras/team.html +96 -10
- nautobot/project-static/docs/user-guide/core-data-model/ipam/ipaddress.html +96 -10
- nautobot/project-static/docs/user-guide/core-data-model/ipam/namespace.html +96 -10
- nautobot/project-static/docs/user-guide/core-data-model/ipam/prefix.html +96 -10
- nautobot/project-static/docs/user-guide/core-data-model/ipam/rir.html +96 -10
- nautobot/project-static/docs/user-guide/core-data-model/ipam/routetarget.html +96 -10
- nautobot/project-static/docs/user-guide/core-data-model/ipam/service.html +96 -10
- nautobot/project-static/docs/user-guide/core-data-model/ipam/vlan.html +96 -10
- nautobot/project-static/docs/user-guide/core-data-model/ipam/vlangroup.html +96 -10
- nautobot/project-static/docs/user-guide/core-data-model/ipam/vrf.html +96 -10
- nautobot/project-static/docs/user-guide/core-data-model/overview/introduction.html +96 -10
- nautobot/project-static/docs/user-guide/core-data-model/tenancy/tenant.html +96 -10
- nautobot/project-static/docs/user-guide/core-data-model/tenancy/tenantgroup.html +96 -10
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/cluster.html +96 -10
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/clustergroup.html +96 -10
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/clustertype.html +96 -10
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/virtualmachine.html +96 -10
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/vminterface.html +96 -10
- nautobot/project-static/docs/user-guide/feature-guides/contacts-and-teams.html +96 -10
- nautobot/project-static/docs/user-guide/feature-guides/custom-fields.html +96 -10
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/creating-devices.html +96 -10
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/creating-location-types-and-locations.html +96 -10
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/index.html +96 -10
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/interfaces.html +96 -10
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/ipam.html +96 -10
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/platforms.html +96 -10
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/search-bar.html +96 -10
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/tenants.html +96 -10
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/vlans-and-vlan-groups.html +96 -10
- nautobot/project-static/docs/user-guide/feature-guides/git-data-source.html +96 -10
- nautobot/project-static/docs/user-guide/feature-guides/graphql.html +96 -10
- nautobot/project-static/docs/user-guide/feature-guides/ip-address-merge-tool.html +96 -10
- nautobot/project-static/docs/user-guide/feature-guides/relationships.html +96 -10
- nautobot/project-static/docs/user-guide/feature-guides/software-image-files-and-versions.html +96 -10
- nautobot/project-static/docs/user-guide/index.html +99 -13
- nautobot/project-static/docs/user-guide/platform-functionality/change-logging.html +96 -10
- nautobot/project-static/docs/user-guide/platform-functionality/computedfield.html +96 -10
- nautobot/project-static/docs/user-guide/platform-functionality/customfield.html +96 -10
- nautobot/project-static/docs/user-guide/platform-functionality/customlink.html +96 -10
- nautobot/project-static/docs/user-guide/platform-functionality/dynamicgroup.html +96 -10
- nautobot/project-static/docs/user-guide/platform-functionality/exporttemplate.html +96 -10
- nautobot/project-static/docs/user-guide/platform-functionality/externalintegration.html +96 -10
- nautobot/project-static/docs/user-guide/platform-functionality/gitrepository.html +96 -10
- nautobot/project-static/docs/user-guide/platform-functionality/graphql.html +96 -10
- nautobot/project-static/docs/user-guide/platform-functionality/graphqlquery.html +96 -10
- nautobot/project-static/docs/user-guide/platform-functionality/imageattachment.html +96 -10
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/index.html +97 -11
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/job-scheduling-and-approvals.html +96 -10
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobbutton.html +101 -14
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobhook.html +96 -10
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/models.html +96 -10
- nautobot/project-static/docs/user-guide/platform-functionality/napalm.html +96 -10
- nautobot/project-static/docs/user-guide/platform-functionality/note.html +96 -10
- nautobot/project-static/docs/user-guide/platform-functionality/relationship.html +96 -10
- nautobot/project-static/docs/user-guide/platform-functionality/rest-api/authentication.html +96 -10
- nautobot/project-static/docs/user-guide/platform-functionality/rest-api/filtering.html +96 -10
- nautobot/project-static/docs/user-guide/platform-functionality/rest-api/overview.html +96 -10
- nautobot/project-static/docs/user-guide/platform-functionality/rest-api/ui-related-endpoints.html +96 -10
- nautobot/project-static/docs/user-guide/platform-functionality/role.html +96 -10
- nautobot/project-static/docs/user-guide/platform-functionality/secret.html +96 -10
- nautobot/project-static/docs/user-guide/platform-functionality/status.html +96 -10
- nautobot/project-static/docs/user-guide/platform-functionality/tag.html +96 -10
- nautobot/project-static/docs/user-guide/platform-functionality/template-filters.html +96 -10
- nautobot/project-static/docs/user-guide/platform-functionality/users/objectpermission.html +96 -10
- nautobot/project-static/docs/user-guide/platform-functionality/users/token.html +96 -10
- nautobot/project-static/docs/user-guide/platform-functionality/webhook.html +96 -10
- nautobot/project-static/js/connection_toggles.js +7 -6
- {nautobot-2.2.5.dist-info → nautobot-2.2.7.dist-info}/METADATA +2 -2
- {nautobot-2.2.5.dist-info → nautobot-2.2.7.dist-info}/RECORD +344 -311
- nautobot/extras/tests/test_git.py +0 -23
- {nautobot-2.2.5.dist-info → nautobot-2.2.7.dist-info}/LICENSE.txt +0 -0
- {nautobot-2.2.5.dist-info → nautobot-2.2.7.dist-info}/NOTICE +0 -0
- {nautobot-2.2.5.dist-info → nautobot-2.2.7.dist-info}/WHEEL +0 -0
- {nautobot-2.2.5.dist-info → nautobot-2.2.7.dist-info}/entry_points.txt +0 -0
nautobot/core/utils/git.py
CHANGED
|
@@ -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
|
|
108
|
+
if commit_hexsha and self.head.startswith(commit_hexsha):
|
|
101
109
|
logger.debug(f"Commit {commit_hexsha} is already checked out.")
|
|
102
|
-
return (
|
|
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
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
if
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
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
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
#
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
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
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
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.
|
|
221
|
+
f" If this is a new repo, please add a commit before syncing."
|
|
155
222
|
)
|
|
156
223
|
|
|
157
|
-
|
|
158
|
-
|
|
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
|
-
|
|
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.
|
|
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 `
|
|
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
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
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(
|
|
295
|
-
#
|
|
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
|
-
#
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
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
|
-
|
|
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
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
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
|
-
|
|
324
|
-
|
|
325
|
-
|
|
282
|
+
dir_path = os.path.join(config_context_path, filter_type)
|
|
283
|
+
if not os.path.isdir(dir_path):
|
|
284
|
+
continue
|
|
326
285
|
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
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
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
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
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
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
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
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
|
-
|
|
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):
|