nautobot 2.2.4__py3-none-any.whl → 2.2.6__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/circuits/forms.py +15 -0
- nautobot/circuits/navigation.py +9 -1
- nautobot/circuits/views.py +2 -0
- nautobot/core/api/fields.py +13 -0
- nautobot/core/api/serializers.py +7 -1
- nautobot/core/filters.py +11 -0
- nautobot/core/management/commands/generate_test_data.py +128 -158
- nautobot/core/models/fields.py +15 -0
- nautobot/core/settings.yaml +3 -3
- nautobot/core/testing/filters.py +24 -1
- nautobot/core/testing/views.py +13 -1
- nautobot/core/tests/test_utils.py +48 -1
- nautobot/core/utils/git.py +121 -49
- nautobot/core/utils/module_loading.py +10 -2
- nautobot/core/views/utils.py +18 -1
- nautobot/dcim/factory.py +1 -1
- nautobot/dcim/filters/__init__.py +1 -1
- nautobot/dcim/forms.py +23 -4
- nautobot/dcim/tables/devicetypes.py +15 -4
- nautobot/dcim/tests/test_models.py +2 -0
- nautobot/dcim/tests/test_views.py +84 -0
- nautobot/dcim/views.py +3 -0
- nautobot/extras/api/views.py +2 -2
- nautobot/extras/context_managers.py +3 -0
- nautobot/extras/datasources/git.py +133 -135
- nautobot/extras/datasources/utils.py +3 -0
- nautobot/extras/filters/__init__.py +16 -1
- nautobot/extras/forms/forms.py +49 -3
- nautobot/extras/forms/mixins.py +0 -6
- 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/jobs.py +30 -0
- nautobot/extras/models/models.py +2 -4
- nautobot/extras/signals.py +6 -1
- 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 +52 -13
- nautobot/extras/tests/test_context_managers.py +33 -1
- nautobot/extras/tests/test_datasources.py +94 -276
- nautobot/extras/tests/test_filters.py +69 -0
- nautobot/extras/tests/test_forms.py +0 -3
- nautobot/extras/tests/test_models.py +8 -3
- nautobot/extras/tests/test_views.py +69 -11
- nautobot/extras/views.py +12 -10
- nautobot/ipam/filters.py +9 -1
- nautobot/ipam/forms.py +26 -0
- nautobot/ipam/tables.py +1 -1
- nautobot/ipam/tests/test_filters.py +15 -0
- nautobot/ipam/tests/test_views.py +9 -2
- nautobot/ipam/views.py +11 -0
- nautobot/project-static/docs/404.html +84 -9
- nautobot/project-static/docs/apps/index.html +97 -11
- nautobot/project-static/docs/apps/nautobot-apps.html +97 -11
- 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.3220b9d7.min.js → bundle.ad660dcc.min.js} +6 -6
- nautobot/project-static/docs/assets/javascripts/{bundle.3220b9d7.min.js.map → bundle.ad660dcc.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/assets/stylesheets/{main.66ac8b77.min.css → main.6543a935.min.css} +1 -1
- nautobot/project-static/docs/assets/stylesheets/{main.66ac8b77.min.css.map → main.6543a935.min.css.map} +1 -1
- nautobot/project-static/docs/code-reference/nautobot/apps/__init__.html +97 -11
- nautobot/project-static/docs/code-reference/nautobot/apps/admin.html +97 -11
- nautobot/project-static/docs/code-reference/nautobot/apps/api.html +97 -11
- nautobot/project-static/docs/code-reference/nautobot/apps/change_logging.html +97 -11
- nautobot/project-static/docs/code-reference/nautobot/apps/choices.html +97 -11
- nautobot/project-static/docs/code-reference/nautobot/apps/config.html +97 -11
- nautobot/project-static/docs/code-reference/nautobot/apps/constants.html +97 -11
- nautobot/project-static/docs/code-reference/nautobot/apps/datasources.html +97 -11
- nautobot/project-static/docs/code-reference/nautobot/apps/exceptions.html +97 -11
- nautobot/project-static/docs/code-reference/nautobot/apps/factory.html +97 -11
- nautobot/project-static/docs/code-reference/nautobot/apps/filters.html +97 -11
- nautobot/project-static/docs/code-reference/nautobot/apps/forms.html +97 -11
- nautobot/project-static/docs/code-reference/nautobot/apps/graphql.html +97 -11
- nautobot/project-static/docs/code-reference/nautobot/apps/jobs.html +97 -11
- nautobot/project-static/docs/code-reference/nautobot/apps/models.html +157 -13
- nautobot/project-static/docs/code-reference/nautobot/apps/querysets.html +97 -11
- nautobot/project-static/docs/code-reference/nautobot/apps/secrets.html +97 -11
- nautobot/project-static/docs/code-reference/nautobot/apps/tables.html +97 -11
- nautobot/project-static/docs/code-reference/nautobot/apps/testing.html +107 -12
- nautobot/project-static/docs/code-reference/nautobot/apps/ui.html +97 -11
- nautobot/project-static/docs/code-reference/nautobot/apps/urls.html +97 -11
- nautobot/project-static/docs/code-reference/nautobot/apps/utils.html +144 -12
- nautobot/project-static/docs/code-reference/nautobot/apps/views.html +97 -11
- nautobot/project-static/docs/development/apps/api/configuration-view.html +97 -11
- nautobot/project-static/docs/development/apps/api/database-backend-config.html +97 -11
- nautobot/project-static/docs/development/apps/api/models/django-admin.html +97 -11
- nautobot/project-static/docs/development/apps/api/models/global-search.html +97 -11
- nautobot/project-static/docs/development/apps/api/models/graphql.html +97 -11
- nautobot/project-static/docs/development/apps/api/models/index.html +97 -11
- nautobot/project-static/docs/development/apps/api/nautobot-app-config.html +97 -11
- nautobot/project-static/docs/development/apps/api/platform-features/custom-validators.html +97 -11
- nautobot/project-static/docs/development/apps/api/platform-features/filter-extensions.html +98 -12
- nautobot/project-static/docs/development/apps/api/platform-features/git-repository-content.html +97 -11
- nautobot/project-static/docs/development/apps/api/platform-features/index.html +97 -11
- nautobot/project-static/docs/development/apps/api/platform-features/jinja2-filters.html +97 -11
- nautobot/project-static/docs/development/apps/api/platform-features/jobs.html +97 -11
- nautobot/project-static/docs/development/apps/api/platform-features/populating-extensibility-features.html +97 -11
- nautobot/project-static/docs/development/apps/api/platform-features/secrets-providers.html +97 -11
- nautobot/project-static/docs/development/apps/api/platform-features/uniquely-identify-objects.html +97 -11
- nautobot/project-static/docs/development/apps/api/prometheus.html +97 -11
- nautobot/project-static/docs/development/apps/api/setup.html +97 -11
- nautobot/project-static/docs/development/apps/api/testing.html +97 -11
- nautobot/project-static/docs/development/apps/api/ui-extensions/banners.html +97 -11
- nautobot/project-static/docs/development/apps/api/ui-extensions/home-page.html +97 -11
- nautobot/project-static/docs/development/apps/api/ui-extensions/index.html +97 -11
- nautobot/project-static/docs/development/apps/api/ui-extensions/navigation.html +97 -11
- nautobot/project-static/docs/development/apps/api/ui-extensions/object-views.html +97 -11
- nautobot/project-static/docs/development/apps/api/views/base-template.html +97 -11
- nautobot/project-static/docs/development/apps/api/views/core-view-overrides.html +97 -11
- nautobot/project-static/docs/development/apps/api/views/django-generic-views.html +97 -11
- nautobot/project-static/docs/development/apps/api/views/help-documentation.html +97 -11
- nautobot/project-static/docs/development/apps/api/views/index.html +97 -11
- nautobot/project-static/docs/development/apps/api/views/nautobot-generic-views.html +97 -11
- nautobot/project-static/docs/development/apps/api/views/nautobotuiviewset.html +97 -11
- nautobot/project-static/docs/development/apps/api/views/nautobotuiviewsetrouter.html +97 -11
- nautobot/project-static/docs/development/apps/api/views/notes.html +97 -11
- nautobot/project-static/docs/development/apps/api/views/rest-api.html +97 -11
- nautobot/project-static/docs/development/apps/api/views/urls.html +97 -11
- nautobot/project-static/docs/development/apps/index.html +97 -11
- nautobot/project-static/docs/development/apps/migration/code-updates.html +98 -12
- nautobot/project-static/docs/development/apps/migration/dependency-updates.html +97 -11
- nautobot/project-static/docs/development/apps/migration/from-v1.html +99 -13
- nautobot/project-static/docs/development/apps/migration/model-updates/dcim.html +97 -11
- nautobot/project-static/docs/development/apps/migration/model-updates/extras.html +97 -11
- nautobot/project-static/docs/development/apps/migration/model-updates/global.html +97 -11
- nautobot/project-static/docs/development/apps/migration/model-updates/ipam.html +97 -11
- nautobot/project-static/docs/development/apps/porting-from-netbox.html +97 -11
- nautobot/project-static/docs/development/core/application-registry.html +98 -12
- nautobot/project-static/docs/development/core/best-practices.html +97 -11
- nautobot/project-static/docs/development/core/bootstrap-ui.html +97 -11
- nautobot/project-static/docs/development/core/caching.html +97 -11
- nautobot/project-static/docs/development/core/controllers.html +97 -11
- nautobot/project-static/docs/development/core/docker-compose-advanced-use-cases.html +97 -11
- nautobot/project-static/docs/development/core/generic-views.html +97 -11
- nautobot/project-static/docs/development/core/getting-started.html +99 -13
- nautobot/project-static/docs/development/core/homepage.html +97 -11
- nautobot/project-static/docs/development/core/index.html +97 -11
- nautobot/project-static/docs/development/core/model-checklist.html +97 -11
- nautobot/project-static/docs/development/core/model-features.html +97 -11
- nautobot/project-static/docs/development/core/natural-keys.html +97 -11
- nautobot/project-static/docs/development/core/navigation-menu.html +97 -11
- nautobot/project-static/docs/development/core/release-checklist.html +97 -11
- nautobot/project-static/docs/development/core/role-internals.html +97 -11
- nautobot/project-static/docs/development/core/settings.html +97 -11
- nautobot/project-static/docs/development/core/style-guide.html +97 -11
- nautobot/project-static/docs/development/core/templates.html +97 -11
- nautobot/project-static/docs/development/core/testing.html +98 -12
- nautobot/project-static/docs/development/core/user-preferences.html +97 -11
- nautobot/project-static/docs/development/index.html +97 -11
- nautobot/project-static/docs/development/jobs/index.html +97 -11
- nautobot/project-static/docs/development/jobs/migration/from-v1.html +97 -11
- 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 +97 -11
- nautobot/project-static/docs/release-notes/version-1.0.html +98 -12
- nautobot/project-static/docs/release-notes/version-1.1.html +97 -11
- nautobot/project-static/docs/release-notes/version-1.2.html +99 -13
- nautobot/project-static/docs/release-notes/version-1.3.html +98 -12
- nautobot/project-static/docs/release-notes/version-1.4.html +99 -13
- nautobot/project-static/docs/release-notes/version-1.5.html +99 -13
- nautobot/project-static/docs/release-notes/version-1.6.html +626 -160
- nautobot/project-static/docs/release-notes/version-2.0.html +100 -14
- nautobot/project-static/docs/release-notes/version-2.1.html +97 -11
- nautobot/project-static/docs/release-notes/version-2.2.html +546 -107
- nautobot/project-static/docs/requirements.txt +3 -2
- 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 +97 -11
- nautobot/project-static/docs/user-guide/administration/configuration/authentication/remote.html +97 -11
- nautobot/project-static/docs/user-guide/administration/configuration/authentication/sso.html +99 -13
- nautobot/project-static/docs/user-guide/administration/configuration/index.html +98 -12
- nautobot/project-static/docs/user-guide/administration/configuration/optional-settings.html +100 -14
- nautobot/project-static/docs/user-guide/administration/configuration/required-settings.html +97 -11
- nautobot/project-static/docs/user-guide/administration/configuration/time-zones.html +97 -11
- nautobot/project-static/docs/user-guide/administration/guides/caching.html +97 -11
- nautobot/project-static/docs/user-guide/administration/guides/celery-queues.html +97 -11
- nautobot/project-static/docs/user-guide/administration/guides/healthcheck.html +97 -11
- nautobot/project-static/docs/user-guide/administration/guides/permissions.html +97 -11
- nautobot/project-static/docs/user-guide/administration/guides/prometheus-metrics.html +97 -11
- nautobot/project-static/docs/user-guide/administration/guides/replicating-nautobot.html +98 -12
- nautobot/project-static/docs/user-guide/administration/guides/request-profiling.html +97 -11
- nautobot/project-static/docs/user-guide/administration/guides/s3-django-storage.html +97 -11
- nautobot/project-static/docs/user-guide/administration/installation/app-install.html +97 -11
- nautobot/project-static/docs/user-guide/administration/installation/external-authentication.html +97 -11
- nautobot/project-static/docs/user-guide/administration/installation/http-server.html +97 -11
- nautobot/project-static/docs/user-guide/administration/installation/index.html +97 -11
- nautobot/project-static/docs/user-guide/administration/installation/install_system.html +97 -11
- nautobot/project-static/docs/user-guide/administration/installation/nautobot.html +101 -15
- nautobot/project-static/docs/user-guide/administration/installation/services.html +98 -12
- nautobot/project-static/docs/user-guide/administration/installation-extras/docker.html +97 -11
- nautobot/project-static/docs/user-guide/administration/installation-extras/health-checks.html +97 -11
- nautobot/project-static/docs/user-guide/administration/installation-extras/selinux-troubleshooting.html +97 -11
- nautobot/project-static/docs/user-guide/administration/migration/migrating-from-netbox.html +97 -11
- nautobot/project-static/docs/user-guide/administration/migration/migrating-from-postgresql.html +97 -11
- nautobot/project-static/docs/user-guide/administration/tools/nautobot-server.html +97 -11
- nautobot/project-static/docs/user-guide/administration/tools/nautobot-shell.html +97 -11
- nautobot/project-static/docs/user-guide/administration/upgrading/database-backup.html +97 -11
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/after-you-upgrade.html +97 -11
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/before-you-upgrade.html +97 -11
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/for-developers.html +98 -12
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/index.html +97 -11
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/whats-changed.html +97 -11
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/region-and-site-data-migration-guide.html +97 -30
- 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 +100 -14
- nautobot/project-static/docs/user-guide/administration/upgrading/upgrading.html +97 -11
- nautobot/project-static/docs/user-guide/core-data-model/circuits/circuit.html +97 -11
- nautobot/project-static/docs/user-guide/core-data-model/circuits/circuittermination.html +97 -11
- nautobot/project-static/docs/user-guide/core-data-model/circuits/circuittype.html +97 -11
- nautobot/project-static/docs/user-guide/core-data-model/circuits/provider.html +97 -11
- nautobot/project-static/docs/user-guide/core-data-model/circuits/providernetwork.html +97 -11
- nautobot/project-static/docs/user-guide/core-data-model/dcim/cable.html +97 -11
- nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleport.html +97 -11
- nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleporttemplate.html +97 -11
- nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleserverport.html +97 -11
- nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleserverporttemplate.html +97 -11
- nautobot/project-static/docs/user-guide/core-data-model/dcim/controller.html +97 -11
- nautobot/project-static/docs/user-guide/core-data-model/dcim/controllermanageddevicegroup.html +97 -11
- nautobot/project-static/docs/user-guide/core-data-model/dcim/device.html +99 -13
- nautobot/project-static/docs/user-guide/core-data-model/dcim/devicebay.html +97 -11
- nautobot/project-static/docs/user-guide/core-data-model/dcim/devicebaytemplate.html +97 -11
- nautobot/project-static/docs/user-guide/core-data-model/dcim/devicefamily.html +97 -11
- nautobot/project-static/docs/user-guide/core-data-model/dcim/deviceredundancygroup.html +97 -11
- nautobot/project-static/docs/user-guide/core-data-model/dcim/devicetype.html +98 -12
- nautobot/project-static/docs/user-guide/core-data-model/dcim/frontport.html +97 -11
- nautobot/project-static/docs/user-guide/core-data-model/dcim/frontporttemplate.html +97 -11
- nautobot/project-static/docs/user-guide/core-data-model/dcim/interface.html +97 -11
- nautobot/project-static/docs/user-guide/core-data-model/dcim/interfaceredundancygroup.html +97 -11
- nautobot/project-static/docs/user-guide/core-data-model/dcim/interfacetemplate.html +97 -11
- nautobot/project-static/docs/user-guide/core-data-model/dcim/inventoryitem.html +97 -11
- nautobot/project-static/docs/user-guide/core-data-model/dcim/location.html +97 -11
- nautobot/project-static/docs/user-guide/core-data-model/dcim/locationtype.html +97 -11
- nautobot/project-static/docs/user-guide/core-data-model/dcim/manufacturer.html +97 -11
- nautobot/project-static/docs/user-guide/core-data-model/dcim/platform.html +97 -11
- nautobot/project-static/docs/user-guide/core-data-model/dcim/powerfeed.html +97 -11
- nautobot/project-static/docs/user-guide/core-data-model/dcim/poweroutlet.html +97 -11
- nautobot/project-static/docs/user-guide/core-data-model/dcim/poweroutlettemplate.html +97 -11
- nautobot/project-static/docs/user-guide/core-data-model/dcim/powerpanel.html +97 -11
- nautobot/project-static/docs/user-guide/core-data-model/dcim/powerport.html +97 -11
- nautobot/project-static/docs/user-guide/core-data-model/dcim/powerporttemplate.html +97 -11
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rack.html +97 -11
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rackgroup.html +97 -11
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rackreservation.html +97 -11
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rearport.html +97 -11
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rearporttemplate.html +97 -11
- nautobot/project-static/docs/user-guide/core-data-model/dcim/softwareimagefile.html +97 -11
- nautobot/project-static/docs/user-guide/core-data-model/dcim/softwareversion.html +97 -11
- nautobot/project-static/docs/user-guide/core-data-model/dcim/virtualchassis.html +97 -11
- nautobot/project-static/docs/user-guide/core-data-model/extras/configcontext.html +97 -11
- nautobot/project-static/docs/user-guide/core-data-model/extras/configcontextschema.html +97 -11
- nautobot/project-static/docs/user-guide/core-data-model/extras/contact.html +97 -11
- nautobot/project-static/docs/user-guide/core-data-model/extras/team.html +97 -11
- nautobot/project-static/docs/user-guide/core-data-model/ipam/ipaddress.html +97 -11
- nautobot/project-static/docs/user-guide/core-data-model/ipam/namespace.html +97 -11
- nautobot/project-static/docs/user-guide/core-data-model/ipam/prefix.html +97 -11
- nautobot/project-static/docs/user-guide/core-data-model/ipam/rir.html +97 -11
- nautobot/project-static/docs/user-guide/core-data-model/ipam/routetarget.html +97 -11
- nautobot/project-static/docs/user-guide/core-data-model/ipam/service.html +97 -11
- nautobot/project-static/docs/user-guide/core-data-model/ipam/vlan.html +97 -11
- nautobot/project-static/docs/user-guide/core-data-model/ipam/vlangroup.html +97 -11
- nautobot/project-static/docs/user-guide/core-data-model/ipam/vrf.html +97 -11
- nautobot/project-static/docs/user-guide/core-data-model/overview/introduction.html +97 -11
- nautobot/project-static/docs/user-guide/core-data-model/tenancy/tenant.html +97 -11
- nautobot/project-static/docs/user-guide/core-data-model/tenancy/tenantgroup.html +97 -11
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/cluster.html +97 -11
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/clustergroup.html +97 -11
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/clustertype.html +97 -11
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/virtualmachine.html +97 -11
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/vminterface.html +97 -11
- nautobot/project-static/docs/user-guide/feature-guides/contacts-and-teams.html +97 -11
- nautobot/project-static/docs/user-guide/feature-guides/custom-fields.html +97 -11
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/creating-devices.html +100 -14
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/creating-location-types-and-locations.html +97 -11
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/index.html +97 -11
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/interfaces.html +98 -12
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/ipam.html +97 -11
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/platforms.html +97 -11
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/search-bar.html +97 -11
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/tenants.html +97 -11
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/vlans-and-vlan-groups.html +98 -12
- nautobot/project-static/docs/user-guide/feature-guides/git-data-source.html +97 -11
- nautobot/project-static/docs/user-guide/feature-guides/graphql.html +97 -11
- nautobot/project-static/docs/user-guide/feature-guides/ip-address-merge-tool.html +97 -11
- nautobot/project-static/docs/user-guide/feature-guides/relationships.html +97 -11
- nautobot/project-static/docs/user-guide/feature-guides/software-image-files-and-versions.html +97 -11
- nautobot/project-static/docs/user-guide/index.html +100 -14
- nautobot/project-static/docs/user-guide/platform-functionality/change-logging.html +97 -11
- nautobot/project-static/docs/user-guide/platform-functionality/computedfield.html +97 -11
- nautobot/project-static/docs/user-guide/platform-functionality/customfield.html +97 -11
- nautobot/project-static/docs/user-guide/platform-functionality/customlink.html +97 -11
- nautobot/project-static/docs/user-guide/platform-functionality/dynamicgroup.html +97 -11
- nautobot/project-static/docs/user-guide/platform-functionality/exporttemplate.html +97 -11
- nautobot/project-static/docs/user-guide/platform-functionality/externalintegration.html +97 -11
- nautobot/project-static/docs/user-guide/platform-functionality/gitrepository.html +97 -11
- nautobot/project-static/docs/user-guide/platform-functionality/graphql.html +97 -11
- nautobot/project-static/docs/user-guide/platform-functionality/graphqlquery.html +97 -11
- nautobot/project-static/docs/user-guide/platform-functionality/imageattachment.html +97 -11
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/index.html +98 -12
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/job-scheduling-and-approvals.html +98 -12
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobbutton.html +102 -15
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobhook.html +97 -11
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/models.html +99 -13
- nautobot/project-static/docs/user-guide/platform-functionality/napalm.html +99 -13
- nautobot/project-static/docs/user-guide/platform-functionality/note.html +97 -11
- nautobot/project-static/docs/user-guide/platform-functionality/relationship.html +97 -11
- nautobot/project-static/docs/user-guide/platform-functionality/rest-api/authentication.html +97 -11
- nautobot/project-static/docs/user-guide/platform-functionality/rest-api/filtering.html +97 -11
- nautobot/project-static/docs/user-guide/platform-functionality/rest-api/overview.html +97 -11
- nautobot/project-static/docs/user-guide/platform-functionality/rest-api/ui-related-endpoints.html +97 -11
- nautobot/project-static/docs/user-guide/platform-functionality/role.html +98 -44
- nautobot/project-static/docs/user-guide/platform-functionality/secret.html +97 -11
- nautobot/project-static/docs/user-guide/platform-functionality/status.html +97 -11
- nautobot/project-static/docs/user-guide/platform-functionality/tag.html +97 -11
- nautobot/project-static/docs/user-guide/platform-functionality/template-filters.html +97 -11
- nautobot/project-static/docs/user-guide/platform-functionality/users/objectpermission.html +97 -11
- nautobot/project-static/docs/user-guide/platform-functionality/users/token.html +97 -11
- nautobot/project-static/docs/user-guide/platform-functionality/webhook.html +97 -11
- nautobot/project-static/js/forms.js +2 -1
- nautobot/tenancy/forms.py +9 -0
- nautobot/tenancy/views.py +1 -0
- nautobot/virtualization/forms.py +18 -6
- nautobot/virtualization/templates/virtualization/clustertype.html +2 -2
- nautobot/virtualization/views.py +2 -0
- {nautobot-2.2.4.dist-info → nautobot-2.2.6.dist-info}/METADATA +1 -1
- {nautobot-2.2.4.dist-info → nautobot-2.2.6.dist-info}/RECORD +364 -331
- nautobot/extras/tests/test_git.py +0 -23
- {nautobot-2.2.4.dist-info → nautobot-2.2.6.dist-info}/LICENSE.txt +0 -0
- {nautobot-2.2.4.dist-info → nautobot-2.2.6.dist-info}/NOTICE +0 -0
- {nautobot-2.2.4.dist-info → nautobot-2.2.6.dist-info}/WHEEL +0 -0
- {nautobot-2.2.4.dist-info → nautobot-2.2.6.dist-info}/entry_points.txt +0 -0
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import json
|
|
2
1
|
import os
|
|
3
2
|
import sys
|
|
4
3
|
import tempfile
|
|
@@ -6,6 +5,7 @@ from unittest import mock
|
|
|
6
5
|
import uuid
|
|
7
6
|
|
|
8
7
|
from celery.exceptions import NotRegistered
|
|
8
|
+
from django.conf import settings
|
|
9
9
|
from django.contrib.contenttypes.models import ContentType
|
|
10
10
|
from django.core.exceptions import ValidationError
|
|
11
11
|
from django.test import RequestFactory
|
|
@@ -31,6 +31,8 @@ from nautobot.extras.models import (
|
|
|
31
31
|
ExportTemplate,
|
|
32
32
|
GitRepository,
|
|
33
33
|
Job,
|
|
34
|
+
JobButton,
|
|
35
|
+
JobHook,
|
|
34
36
|
JobLogEntry,
|
|
35
37
|
JobResult,
|
|
36
38
|
Role,
|
|
@@ -39,10 +41,10 @@ from nautobot.extras.models import (
|
|
|
39
41
|
SecretsGroupAssociation,
|
|
40
42
|
Status,
|
|
41
43
|
)
|
|
44
|
+
from nautobot.extras.tests.git_helper import create_and_populate_git_repository
|
|
42
45
|
from nautobot.ipam.models import VLAN
|
|
43
46
|
|
|
44
47
|
|
|
45
|
-
@mock.patch("nautobot.extras.datasources.git.GitRepo")
|
|
46
48
|
class GitTest(TransactionTestCase):
|
|
47
49
|
"""
|
|
48
50
|
Tests for Git repository handling.
|
|
@@ -51,7 +53,6 @@ class GitTest(TransactionTestCase):
|
|
|
51
53
|
"""
|
|
52
54
|
|
|
53
55
|
databases = ("default", "job_logs")
|
|
54
|
-
COMMIT_HEXSHA = "88dd9cd78df89e887ee90a1d209a3e9a04e8c841"
|
|
55
56
|
|
|
56
57
|
def setUp(self):
|
|
57
58
|
super().setUp()
|
|
@@ -80,11 +81,15 @@ class GitTest(TransactionTestCase):
|
|
|
80
81
|
status=status,
|
|
81
82
|
)
|
|
82
83
|
|
|
84
|
+
self.tempdir = tempfile.TemporaryDirectory() # pylint: disable=consider-using-with
|
|
85
|
+
create_and_populate_git_repository(self.tempdir.name)
|
|
86
|
+
|
|
83
87
|
self.repo_slug = "test_git_repo"
|
|
84
88
|
self.repo = GitRepository(
|
|
85
89
|
name="Test Git Repository",
|
|
86
90
|
slug=self.repo_slug,
|
|
87
|
-
remote_url="
|
|
91
|
+
remote_url="file://" + self.tempdir.name, # file:// URLs aren't permitted normally, but very useful here!
|
|
92
|
+
branch="empty-repo",
|
|
88
93
|
# Provide everything we know we can provide
|
|
89
94
|
provided_contents=[entry.content_identifier for entry in get_datasource_contents("extras.gitrepository")],
|
|
90
95
|
)
|
|
@@ -92,138 +97,34 @@ class GitTest(TransactionTestCase):
|
|
|
92
97
|
|
|
93
98
|
self.job_result = JobResult.objects.create(name=self.repo.name)
|
|
94
99
|
|
|
95
|
-
self.config_context_schema = {
|
|
96
|
-
"_metadata": {
|
|
97
|
-
"name": "Config Context Schema 1",
|
|
98
|
-
"description": "Schema for defining first names, last names and ages.",
|
|
99
|
-
},
|
|
100
|
-
"data_schema": {
|
|
101
|
-
"title": "Person",
|
|
102
|
-
"type": "object",
|
|
103
|
-
"properties": {
|
|
104
|
-
"firstName": {
|
|
105
|
-
"type": "string",
|
|
106
|
-
"description": "The person's first name.",
|
|
107
|
-
},
|
|
108
|
-
"lastName": {
|
|
109
|
-
"type": "string",
|
|
110
|
-
"description": "The person's last name.",
|
|
111
|
-
},
|
|
112
|
-
"age": {
|
|
113
|
-
"description": "Age in years which must be equal to or greater than zero.",
|
|
114
|
-
"type": "integer",
|
|
115
|
-
"minimum": 0,
|
|
116
|
-
},
|
|
117
|
-
},
|
|
118
|
-
},
|
|
119
|
-
}
|
|
120
|
-
|
|
121
100
|
def tearDown(self):
|
|
122
101
|
if f"{self.repo_slug}.jobs" in sys.modules:
|
|
123
102
|
del sys.modules[f"{self.repo_slug}.jobs"]
|
|
124
103
|
if f"{self.repo_slug}" in sys.modules:
|
|
125
104
|
del sys.modules[f"{self.repo_slug}"]
|
|
105
|
+
self.tempdir.cleanup()
|
|
126
106
|
if self.repo is not None:
|
|
127
107
|
self.repo.delete()
|
|
128
108
|
super().tearDown()
|
|
129
109
|
|
|
130
|
-
def populate_repo(self, path, url, *args, **kwargs):
|
|
131
|
-
os.makedirs(path, exist_ok=True)
|
|
132
|
-
|
|
133
|
-
os.makedirs(os.path.join(path, "config_contexts"), exist_ok=True)
|
|
134
|
-
os.makedirs(os.path.join(path, "config_contexts", "devices"), exist_ok=True)
|
|
135
|
-
os.makedirs(os.path.join(path, "config_contexts", "locations"), exist_ok=True)
|
|
136
|
-
os.makedirs(os.path.join(path, "config_context_schemas"), exist_ok=True)
|
|
137
|
-
os.makedirs(os.path.join(path, "export_templates", "dcim", "device"), exist_ok=True)
|
|
138
|
-
os.makedirs(os.path.join(path, "export_templates", "ipam", "vlan"), exist_ok=True)
|
|
139
|
-
os.makedirs(os.path.join(path, "jobs"), exist_ok=True)
|
|
140
|
-
|
|
141
|
-
with open(os.path.join(path, "__init__.py"), "w") as fd:
|
|
142
|
-
# Required for job importing
|
|
143
|
-
pass
|
|
144
|
-
|
|
145
|
-
with open(os.path.join(path, "config_contexts", "context.yaml"), "w") as fd:
|
|
146
|
-
yaml.dump(
|
|
147
|
-
{
|
|
148
|
-
"_metadata": {
|
|
149
|
-
"name": "Frobozz 1000 NTP servers",
|
|
150
|
-
"weight": 1500,
|
|
151
|
-
"description": "NTP servers for Frobozz 1000 devices **only**",
|
|
152
|
-
"is_active": True,
|
|
153
|
-
"config_context_schema": "Config Context Schema 1",
|
|
154
|
-
"device_types": [{"model": self.device_type.model}],
|
|
155
|
-
},
|
|
156
|
-
"ntp-servers": ["172.16.10.22", "172.16.10.33"],
|
|
157
|
-
},
|
|
158
|
-
fd,
|
|
159
|
-
)
|
|
160
|
-
|
|
161
|
-
with open(os.path.join(path, "config_contexts", "locations", f"{self.location.name}.json"), "w") as fd:
|
|
162
|
-
json.dump(
|
|
163
|
-
{
|
|
164
|
-
"_metadata": {"name": "Location context", "is_active": False},
|
|
165
|
-
"domain_name": "example.com",
|
|
166
|
-
},
|
|
167
|
-
fd,
|
|
168
|
-
)
|
|
169
|
-
|
|
170
|
-
with open(os.path.join(path, "config_contexts", "devices", f"{self.device.name}.json"), "w") as fd:
|
|
171
|
-
json.dump({"dns-servers": ["8.8.8.8"]}, fd)
|
|
172
|
-
|
|
173
|
-
with open(os.path.join(path, "config_context_schemas", "schema-1.yaml"), "w") as fd:
|
|
174
|
-
yaml.dump(self.config_context_schema, fd)
|
|
175
|
-
|
|
176
|
-
with open(os.path.join(path, "export_templates", "dcim", "device", "template.j2"), "w") as fd:
|
|
177
|
-
fd.write("{% for device in queryset %}\n{{ device.name }}\n{% endfor %}")
|
|
178
|
-
|
|
179
|
-
with open(os.path.join(path, "export_templates", "dcim", "device", "template2.html"), "w") as fd:
|
|
180
|
-
fd.write("<!DOCTYPE html>/n{% for device in queryset %}\n{{ device.name }}\n{% endfor %}")
|
|
181
|
-
|
|
182
|
-
with open(os.path.join(path, "export_templates", "ipam", "vlan", "template.j2"), "w") as fd:
|
|
183
|
-
fd.write("{% for vlan in queryset %}\n{{ vlan.name }}\n{% endfor %}")
|
|
184
|
-
|
|
185
|
-
with open(os.path.join(path, "jobs", "__init__.py"), "w") as fd:
|
|
186
|
-
fd.write("from nautobot.core.celery import register_jobs\nfrom .my_job import MyJob\nregister_jobs(MyJob)")
|
|
187
|
-
|
|
188
|
-
with open(os.path.join(path, "jobs", "my_job.py"), "w") as fd:
|
|
189
|
-
fd.write("from nautobot.extras.jobs import Job\nclass MyJob(Job):\n def run(self):\n pass")
|
|
190
|
-
|
|
191
|
-
return mock.DEFAULT
|
|
192
|
-
|
|
193
|
-
def empty_repo(self, path, url, *args, **kwargs):
|
|
194
|
-
try:
|
|
195
|
-
os.remove(os.path.join(path, "__init__.py"))
|
|
196
|
-
os.remove(os.path.join(path, "config_contexts", "context.yaml"))
|
|
197
|
-
os.remove(os.path.join(path, "config_contexts", "locations", f"{self.location.name}.json"))
|
|
198
|
-
os.remove(os.path.join(path, "config_contexts", "devices", f"{self.device.name}.json"))
|
|
199
|
-
os.remove(os.path.join(path, "config_context_schemas", "schema-1.yaml"))
|
|
200
|
-
os.remove(os.path.join(path, "export_templates", "dcim", "device", "template.j2"))
|
|
201
|
-
os.remove(os.path.join(path, "export_templates", "dcim", "device", "template2.html"))
|
|
202
|
-
os.remove(os.path.join(path, "export_templates", "ipam", "vlan", "template.j2"))
|
|
203
|
-
os.remove(os.path.join(path, "jobs", "__init__.py"))
|
|
204
|
-
os.remove(os.path.join(path, "jobs", "my_job.py"))
|
|
205
|
-
except FileNotFoundError:
|
|
206
|
-
pass
|
|
207
|
-
return mock.DEFAULT
|
|
208
|
-
|
|
209
110
|
def assert_repo_slug_valid_python_package_name(self):
|
|
210
111
|
git_repository = GitRepository.objects.create(
|
|
211
112
|
name="1 Very-Bad Git_____Repo Name (2)", remote_url="http://localhost/git.git"
|
|
212
113
|
)
|
|
213
114
|
self.assertEqual(git_repository.slug, "a1_very_bad_git_____repo_name_2")
|
|
214
115
|
|
|
215
|
-
def assert_config_context_schema_record_exists(self, name):
|
|
216
|
-
"""
|
|
116
|
+
def assert_config_context_schema_record_exists(self, name, filename="schema-1.yaml"):
|
|
117
|
+
"""Assert that a ConfigContextSchema record exists with the expected name and data_schema."""
|
|
217
118
|
config_context_schema_record = ConfigContextSchema.objects.get(
|
|
218
119
|
name=name,
|
|
219
120
|
owner_object_id=self.repo.pk,
|
|
220
121
|
owner_content_type=ContentType.objects.get_for_model(GitRepository),
|
|
221
122
|
)
|
|
222
|
-
config_context_schema = self.config_context_schema
|
|
223
|
-
config_context_schema_metadata = config_context_schema["_metadata"]
|
|
224
123
|
self.assertIsNotNone(config_context_schema_record)
|
|
225
|
-
self.
|
|
226
|
-
|
|
124
|
+
with open(os.path.join(settings.GIT_ROOT, self.repo.slug, "config_context_schemas", filename)) as fd:
|
|
125
|
+
config_context_schema_data = yaml.safe_load(fd)
|
|
126
|
+
self.assertEqual(config_context_schema_data["_metadata"]["name"], config_context_schema_record.name)
|
|
127
|
+
self.assertEqual(config_context_schema_data["data_schema"], config_context_schema_record.data_schema)
|
|
227
128
|
|
|
228
129
|
def assert_device_exists(self, name):
|
|
229
130
|
"""Helper function to assert device exists"""
|
|
@@ -258,7 +159,7 @@ class GitTest(TransactionTestCase):
|
|
|
258
159
|
{"ntp-servers": ["172.16.10.22", "172.16.10.33"]},
|
|
259
160
|
config_context.data,
|
|
260
161
|
)
|
|
261
|
-
self.
|
|
162
|
+
self.assertIsNotNone(config_context.config_context_schema)
|
|
262
163
|
|
|
263
164
|
def assert_implicit_config_context_exists(self, name):
|
|
264
165
|
"""Helper function to assert that an 'implicit' ConfigContext exists and is configured appropriately."""
|
|
@@ -296,10 +197,10 @@ class GitTest(TransactionTestCase):
|
|
|
296
197
|
)
|
|
297
198
|
self.assertIsNotNone(export_template_vlan)
|
|
298
199
|
|
|
299
|
-
def assert_job_exists(self, installed=True):
|
|
200
|
+
def assert_job_exists(self, name="MyJob", installed=True):
|
|
300
201
|
"""Helper function to assert JobModel and registered Job exist."""
|
|
301
202
|
# Is it registered correctly in the database?
|
|
302
|
-
job_model = Job.objects.get(name=
|
|
203
|
+
job_model = Job.objects.get(name=name, module_name=f"{self.repo_slug}.jobs.my_job", job_class_name=name)
|
|
303
204
|
self.assertIsNotNone(job_model)
|
|
304
205
|
if installed:
|
|
305
206
|
self.assertTrue(job_model.installed)
|
|
@@ -312,20 +213,12 @@ class GitTest(TransactionTestCase):
|
|
|
312
213
|
with self.assertRaises(NotRegistered):
|
|
313
214
|
job_model.job_task
|
|
314
215
|
|
|
315
|
-
def test_pull_git_repository_and_refresh_data_with_no_data(self
|
|
216
|
+
def test_pull_git_repository_and_refresh_data_with_no_data(self):
|
|
316
217
|
"""
|
|
317
218
|
The pull_git_repository_and_refresh_data job should fail if the given repo is empty.
|
|
318
219
|
"""
|
|
319
220
|
with tempfile.TemporaryDirectory() as tempdir:
|
|
320
221
|
with self.settings(GIT_ROOT=tempdir):
|
|
321
|
-
|
|
322
|
-
def create_empty_repo(path, url):
|
|
323
|
-
os.makedirs(path, exist_ok=True)
|
|
324
|
-
return mock.DEFAULT
|
|
325
|
-
|
|
326
|
-
MockGitRepo.side_effect = create_empty_repo
|
|
327
|
-
MockGitRepo.return_value.checkout.return_value = (self.COMMIT_HEXSHA, True)
|
|
328
|
-
|
|
329
222
|
# Run the Git operation and refresh the object from the DB
|
|
330
223
|
# pull_git_repository_and_refresh_data(self.repo.pk, self.mock_request, self.job_result.pk)
|
|
331
224
|
job_result = create_job_result_and_run_job(
|
|
@@ -341,8 +234,6 @@ class GitTest(TransactionTestCase):
|
|
|
341
234
|
(job_result.result, list(job_result.job_log_entries.values_list("message", "log_object"))),
|
|
342
235
|
)
|
|
343
236
|
self.repo.refresh_from_db()
|
|
344
|
-
self.assertEqual(self.repo.current_head, self.COMMIT_HEXSHA, job_result.result)
|
|
345
|
-
MockGitRepo.assert_called_with(os.path.join(tempdir, self.repo.slug), "http://localhost/git.git")
|
|
346
237
|
|
|
347
238
|
log_entries = JobLogEntry.objects.filter(job_result=job_result)
|
|
348
239
|
failure_logs = log_entries.filter(log_level=LogLevelChoices.LOG_ERROR)
|
|
@@ -354,20 +245,14 @@ class GitTest(TransactionTestCase):
|
|
|
354
245
|
print(job_result.traceback)
|
|
355
246
|
raise
|
|
356
247
|
|
|
248
|
+
@mock.patch("nautobot.extras.datasources.git.GitRepo")
|
|
357
249
|
def test_pull_git_repository_and_refresh_data_with_secrets(self, MockGitRepo):
|
|
358
250
|
"""
|
|
359
251
|
The pull_git_repository_and_refresh_data job should correctly make use of secrets.
|
|
360
252
|
"""
|
|
361
253
|
with tempfile.TemporaryDirectory() as tempdir:
|
|
362
254
|
with self.settings(GIT_ROOT=tempdir):
|
|
363
|
-
|
|
364
|
-
def create_empty_repo(path, url):
|
|
365
|
-
os.makedirs(path, exist_ok=True)
|
|
366
|
-
return mock.DEFAULT
|
|
367
|
-
|
|
368
|
-
MockGitRepo.side_effect = create_empty_repo
|
|
369
|
-
MockGitRepo.return_value.checkout.return_value = (self.COMMIT_HEXSHA, True)
|
|
370
|
-
|
|
255
|
+
MockGitRepo.return_value.checkout.return_value = ("0123456789abcdef", True)
|
|
371
256
|
with open(os.path.join(tempdir, "username.txt"), "wt") as handle:
|
|
372
257
|
handle.write("núñez")
|
|
373
258
|
|
|
@@ -399,6 +284,7 @@ class GitTest(TransactionTestCase):
|
|
|
399
284
|
)
|
|
400
285
|
|
|
401
286
|
self.repo.secrets_group = secrets_group
|
|
287
|
+
self.repo.remote_url = "http://localhost/git.git"
|
|
402
288
|
self.repo.provided_contents.remove("extras.job") # avoid failing due to lack of jobs module
|
|
403
289
|
self.repo.save()
|
|
404
290
|
|
|
@@ -422,16 +308,15 @@ class GitTest(TransactionTestCase):
|
|
|
422
308
|
"http://n%C3%BA%C3%B1ez:1%3A3%40%2F%3F%3Dab%40@localhost/git.git",
|
|
423
309
|
)
|
|
424
310
|
|
|
425
|
-
def test_pull_git_repository_and_refresh_data_with_valid_data(self
|
|
311
|
+
def test_pull_git_repository_and_refresh_data_with_valid_data(self):
|
|
426
312
|
"""
|
|
427
313
|
The test_pull_git_repository_and_refresh_data job should succeed if valid data is present in the repo.
|
|
428
314
|
"""
|
|
429
315
|
with tempfile.TemporaryDirectory() as tempdir:
|
|
430
316
|
with self.settings(GIT_ROOT=tempdir):
|
|
431
|
-
MockGitRepo.side_effect = self.populate_repo
|
|
432
|
-
MockGitRepo.return_value.checkout.return_value = (self.COMMIT_HEXSHA, True)
|
|
433
|
-
|
|
434
317
|
# Run the Git operation and refresh the object from the DB
|
|
318
|
+
self.repo.branch = "valid-files" # actually a tag
|
|
319
|
+
self.repo.save()
|
|
435
320
|
job_model = GitRepositorySync().job_model
|
|
436
321
|
job_result = run_job_for_testing(job=job_model, repository=self.repo.pk)
|
|
437
322
|
job_result.refresh_from_db()
|
|
@@ -462,14 +347,23 @@ class GitTest(TransactionTestCase):
|
|
|
462
347
|
# Case when ContentType.model != ContentType.name, template was added and deleted during sync (#570)
|
|
463
348
|
self.assert_export_template_vlan_exists("template.j2")
|
|
464
349
|
|
|
465
|
-
# Make sure
|
|
466
|
-
self.assert_job_exists()
|
|
350
|
+
# Make sure Jobs were successfully loaded from file and registered as JobModels
|
|
351
|
+
self.assert_job_exists(name="MyJob")
|
|
352
|
+
self.assert_job_exists(name="MyJobButtonReceiver")
|
|
353
|
+
self.assert_job_exists(name="MyJobHookReceiver")
|
|
467
354
|
|
|
468
|
-
#
|
|
469
|
-
|
|
355
|
+
# Create JobButton and JobHook
|
|
356
|
+
JobButton.objects.create(
|
|
357
|
+
name="MyJobButton", enabled=True, text="Click me", job=Job.objects.get(name="MyJobButtonReceiver")
|
|
358
|
+
)
|
|
359
|
+
JobHook.objects.create(name="MyJobHook", enabled=True, job=Job.objects.get(name="MyJobHookReceiver"))
|
|
470
360
|
|
|
471
|
-
#
|
|
472
|
-
|
|
361
|
+
# TODO: test successful sync against a branch name or a commit hash as well
|
|
362
|
+
|
|
363
|
+
# Now "resync" the repository, but now those files no longer exist in the repository
|
|
364
|
+
self.repo.refresh_from_db()
|
|
365
|
+
self.repo.branch = "empty-repo" # actually a tag
|
|
366
|
+
self.repo.save()
|
|
473
367
|
|
|
474
368
|
# Run the Git operation and refresh the object from the DB
|
|
475
369
|
job_result = run_job_for_testing(job=job_model, repository=self.repo.pk)
|
|
@@ -489,6 +383,7 @@ class GitTest(TransactionTestCase):
|
|
|
489
383
|
owner_object_id=self.repo.pk,
|
|
490
384
|
)
|
|
491
385
|
),
|
|
386
|
+
list(job_result.job_log_entries.values_list("message", flat=True)),
|
|
492
387
|
)
|
|
493
388
|
self.assertEqual(
|
|
494
389
|
[],
|
|
@@ -498,6 +393,7 @@ class GitTest(TransactionTestCase):
|
|
|
498
393
|
owner_object_id=self.repo.pk,
|
|
499
394
|
)
|
|
500
395
|
),
|
|
396
|
+
list(job_result.job_log_entries.values_list("message", flat=True)),
|
|
501
397
|
)
|
|
502
398
|
device = Device.objects.get(name=self.device.name)
|
|
503
399
|
self.assertIsNone(device.local_config_context_data)
|
|
@@ -505,61 +401,23 @@ class GitTest(TransactionTestCase):
|
|
|
505
401
|
|
|
506
402
|
# Verify that Job database record still exists but code is no longer installed/loaded
|
|
507
403
|
self.assert_job_exists(installed=False)
|
|
404
|
+
self.assert_job_exists(name="MyJobButtonReceiver", installed=False)
|
|
405
|
+
self.assert_job_exists(name="MyJobHookReceiver", installed=False)
|
|
406
|
+
|
|
407
|
+
# Verify that JobButton and JobHook are auto-disabled since the jobs are no longer available
|
|
408
|
+
jb = JobButton.objects.get(name="MyJobButton")
|
|
409
|
+
self.assertFalse(jb.enabled)
|
|
410
|
+
jh = JobHook.objects.get(name="MyJobHook")
|
|
411
|
+
self.assertFalse(jh.enabled)
|
|
508
412
|
|
|
509
|
-
def test_pull_git_repository_and_refresh_data_with_bad_data(self
|
|
413
|
+
def test_pull_git_repository_and_refresh_data_with_bad_data(self):
|
|
510
414
|
"""
|
|
511
415
|
The test_pull_git_repository_and_refresh_data job should gracefully handle bad data in the Git repository
|
|
512
416
|
"""
|
|
513
417
|
with tempfile.TemporaryDirectory() as tempdir:
|
|
514
418
|
with self.settings(GIT_ROOT=tempdir):
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
os.makedirs(path, exist_ok=True)
|
|
518
|
-
os.makedirs(os.path.join(path, "config_contexts"), exist_ok=True)
|
|
519
|
-
os.makedirs(os.path.join(path, "config_contexts", "devices"), exist_ok=True)
|
|
520
|
-
os.makedirs(os.path.join(path, "config_context_schemas"), exist_ok=True)
|
|
521
|
-
os.makedirs(os.path.join(path, "export_templates", "nosuchapp", "device"), exist_ok=True)
|
|
522
|
-
os.makedirs(os.path.join(path, "export_templates", "dcim", "nosuchmodel"), exist_ok=True)
|
|
523
|
-
os.makedirs(os.path.join(path, "jobs"), exist_ok=True)
|
|
524
|
-
# Incorrect directories
|
|
525
|
-
os.makedirs(os.path.join(path, "devices"), exist_ok=True)
|
|
526
|
-
os.makedirs(os.path.join(path, "dcim"), exist_ok=True)
|
|
527
|
-
with open(os.path.join(path, "__init__.py"), "w") as fd:
|
|
528
|
-
pass
|
|
529
|
-
# Malformed JSON
|
|
530
|
-
with open(os.path.join(path, "config_contexts", "context.json"), "w") as fd:
|
|
531
|
-
fd.write('{"data": ')
|
|
532
|
-
# Valid JSON but missing required keys
|
|
533
|
-
with open(os.path.join(path, "config_contexts", "context2.json"), "w") as fd:
|
|
534
|
-
fd.write("{}")
|
|
535
|
-
with open(os.path.join(path, "config_contexts", "context3.json"), "w") as fd:
|
|
536
|
-
fd.write('{"_metadata": {}}')
|
|
537
|
-
# Malformed JSON
|
|
538
|
-
with open(os.path.join(path, "config_context_schemas", "schema-1.yaml"), "w") as fd:
|
|
539
|
-
fd.write('{"data": ')
|
|
540
|
-
# Valid JSON but missing required keys
|
|
541
|
-
with open(os.path.join(path, "config_context_schemas", "schema-2.yaml"), "w") as fd:
|
|
542
|
-
fd.write("{}")
|
|
543
|
-
# No such device
|
|
544
|
-
with open(os.path.join(path, "config_contexts", "devices", "nosuchdevice.json"), "w") as fd:
|
|
545
|
-
fd.write("{}")
|
|
546
|
-
# Invalid paths
|
|
547
|
-
with open(os.path.join(path, "export_templates", "nosuchapp", "device", "template.j2"), "w") as fd:
|
|
548
|
-
fd.write("{% for device in queryset %}\n{{ device.name }}\n{% endfor %}")
|
|
549
|
-
with open(os.path.join(path, "export_templates", "dcim", "nosuchmodel", "template.j2"), "w") as fd:
|
|
550
|
-
fd.write("{% for device in queryset %}\n{{ device.name }}\n{% endfor %}")
|
|
551
|
-
# Malformed Python
|
|
552
|
-
with open(os.path.join(path, "jobs", "syntaxerror.py"), "w") as fd:
|
|
553
|
-
fd.write("print(")
|
|
554
|
-
with open(os.path.join(path, "jobs", "importerror.py"), "w") as fd:
|
|
555
|
-
fd.write("import nosuchmodule")
|
|
556
|
-
with open(os.path.join(path, "jobs", "__init__.py"), "w") as fd:
|
|
557
|
-
fd.write("import .syntaxerror\nimport .importerror")
|
|
558
|
-
return mock.DEFAULT
|
|
559
|
-
|
|
560
|
-
MockGitRepo.side_effect = populate_repo
|
|
561
|
-
MockGitRepo.return_value.checkout.return_value = (self.COMMIT_HEXSHA, True)
|
|
562
|
-
|
|
419
|
+
self.repo.branch = "invalid-files"
|
|
420
|
+
self.repo.save()
|
|
563
421
|
# Run the Git operation and refresh the object from the DB
|
|
564
422
|
job_model = GitRepositorySync().job_model
|
|
565
423
|
self.assertIsNotNone(job_model)
|
|
@@ -604,25 +462,25 @@ class GitTest(TransactionTestCase):
|
|
|
604
462
|
try:
|
|
605
463
|
failure_logs.get(
|
|
606
464
|
grouping="config context schemas",
|
|
607
|
-
message__contains="Error in loading config context schema data from `
|
|
465
|
+
message__contains="Error in loading config context schema data from `badschema1.json`",
|
|
608
466
|
)
|
|
609
467
|
failure_logs.get(
|
|
610
468
|
grouping="config context schemas",
|
|
611
|
-
message__contains="Error in loading config context schema data from `
|
|
469
|
+
message__contains="Error in loading config context schema data from `badschema2.json`: "
|
|
612
470
|
"data is missing the required `_metadata` key",
|
|
613
471
|
)
|
|
614
472
|
failure_logs.get(
|
|
615
473
|
grouping="config contexts",
|
|
616
|
-
message__contains="Error in loading config context data from `
|
|
474
|
+
message__contains="Error in loading config context data from `badcontext1.json`",
|
|
617
475
|
)
|
|
618
476
|
failure_logs.get(
|
|
619
477
|
grouping="config contexts",
|
|
620
|
-
message__contains="Error in loading config context data from `
|
|
478
|
+
message__contains="Error in loading config context data from `badcontext2.json`: "
|
|
621
479
|
"data is missing the required `_metadata` key",
|
|
622
480
|
)
|
|
623
481
|
failure_logs.get(
|
|
624
482
|
grouping="config contexts",
|
|
625
|
-
message__contains="Error in loading config context data from `
|
|
483
|
+
message__contains="Error in loading config context data from `badcontext3.json`: "
|
|
626
484
|
"data `_metadata` is missing the required `name` key",
|
|
627
485
|
)
|
|
628
486
|
failure_logs.get(
|
|
@@ -640,67 +498,14 @@ class GitTest(TransactionTestCase):
|
|
|
640
498
|
print(job_result.traceback)
|
|
641
499
|
raise
|
|
642
500
|
|
|
643
|
-
def test_delete_git_repository_cleanup(self
|
|
501
|
+
def test_delete_git_repository_cleanup(self):
|
|
644
502
|
"""
|
|
645
503
|
When deleting a GitRepository record, the data that it owned should also be deleted.
|
|
646
504
|
"""
|
|
647
505
|
with tempfile.TemporaryDirectory() as tempdir:
|
|
648
506
|
with self.settings(GIT_ROOT=tempdir):
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
os.makedirs(path, exist_ok=True)
|
|
652
|
-
os.makedirs(os.path.join(path, "config_contexts"), exist_ok=True)
|
|
653
|
-
os.makedirs(os.path.join(path, "config_contexts", "devices"), exist_ok=True)
|
|
654
|
-
os.makedirs(os.path.join(path, "config_context_schemas"), exist_ok=True)
|
|
655
|
-
os.makedirs(os.path.join(path, "export_templates", "dcim", "device"), exist_ok=True)
|
|
656
|
-
os.makedirs(os.path.join(path, "jobs"), exist_ok=True)
|
|
657
|
-
with open(os.path.join(path, "__init__.py"), "w") as fd:
|
|
658
|
-
pass
|
|
659
|
-
with open(os.path.join(path, "config_contexts", "context.yaml"), "w") as fd:
|
|
660
|
-
yaml.dump(
|
|
661
|
-
{
|
|
662
|
-
"_metadata": {
|
|
663
|
-
"name": "Region NYC servers",
|
|
664
|
-
"weight": 1500,
|
|
665
|
-
"description": "NTP servers for region NYC",
|
|
666
|
-
"is_active": True,
|
|
667
|
-
# Changing this from `config_context_schema` to `schema` to assert that schema can
|
|
668
|
-
# be used inplace of `config_context_schema`.
|
|
669
|
-
# TODO(timizuo): Replace `schema` with `config_context_schema` when `schema`
|
|
670
|
-
# backwards-compatibility is removed.
|
|
671
|
-
"schema": "Config Context Schema 1",
|
|
672
|
-
},
|
|
673
|
-
"ntp-servers": ["172.16.10.22", "172.16.10.33"],
|
|
674
|
-
},
|
|
675
|
-
fd,
|
|
676
|
-
)
|
|
677
|
-
with open(
|
|
678
|
-
os.path.join(path, "config_contexts", "devices", "test-device.json"),
|
|
679
|
-
"w",
|
|
680
|
-
) as fd:
|
|
681
|
-
json.dump({"dns-servers": ["8.8.8.8"]}, fd)
|
|
682
|
-
with open(os.path.join(path, "config_context_schemas", "schema-1.yaml"), "w") as fd:
|
|
683
|
-
yaml.dump(self.config_context_schema, fd)
|
|
684
|
-
with open(
|
|
685
|
-
os.path.join(path, "export_templates", "dcim", "device", "template.j2"),
|
|
686
|
-
"w",
|
|
687
|
-
) as fd:
|
|
688
|
-
fd.write("{% for device in queryset %}\n{{ device.name }}\n{% endfor %}")
|
|
689
|
-
with open(os.path.join(path, "jobs", "__init__.py"), "w") as fd:
|
|
690
|
-
fd.write(
|
|
691
|
-
"from nautobot.core.celery import register_jobs\nfrom .my_job import MyJob\nregister_jobs(MyJob)"
|
|
692
|
-
)
|
|
693
|
-
|
|
694
|
-
with open(os.path.join(path, "jobs", "my_job.py"), "w") as fd:
|
|
695
|
-
fd.write(
|
|
696
|
-
"from nautobot.extras.jobs import Job\nclass MyJob(Job):\n def run(self):\n pass"
|
|
697
|
-
)
|
|
698
|
-
|
|
699
|
-
return mock.DEFAULT
|
|
700
|
-
|
|
701
|
-
MockGitRepo.side_effect = populate_repo
|
|
702
|
-
MockGitRepo.return_value.checkout.return_value = (self.COMMIT_HEXSHA, True)
|
|
703
|
-
|
|
507
|
+
self.repo.branch = "valid-files"
|
|
508
|
+
self.repo.save()
|
|
704
509
|
# Run the Git operation and refresh the object from the DB
|
|
705
510
|
job_model = GitRepositorySync().job_model
|
|
706
511
|
job_result = run_job_for_testing(
|
|
@@ -717,13 +522,13 @@ class GitTest(TransactionTestCase):
|
|
|
717
522
|
|
|
718
523
|
# Make sure ConfigContext was successfully loaded from file
|
|
719
524
|
config_context = ConfigContext.objects.get(
|
|
720
|
-
name="
|
|
525
|
+
name="Frobozz 1000 NTP servers",
|
|
721
526
|
owner_object_id=self.repo.pk,
|
|
722
527
|
owner_content_type=ContentType.objects.get_for_model(GitRepository),
|
|
723
528
|
)
|
|
724
529
|
self.assertIsNotNone(config_context)
|
|
725
530
|
self.assertEqual(1500, config_context.weight)
|
|
726
|
-
self.assertEqual("NTP servers for
|
|
531
|
+
self.assertEqual("NTP servers for Frobozz 1000 devices **only**", config_context.description)
|
|
727
532
|
self.assertTrue(config_context.is_active)
|
|
728
533
|
self.assertEqual(
|
|
729
534
|
{"ntp-servers": ["172.16.10.22", "172.16.10.33"]},
|
|
@@ -775,19 +580,13 @@ class GitTest(TransactionTestCase):
|
|
|
775
580
|
|
|
776
581
|
self.assert_job_exists(installed=False)
|
|
777
582
|
|
|
778
|
-
def test_git_dry_run(self
|
|
583
|
+
def test_git_dry_run(self):
|
|
779
584
|
with tempfile.TemporaryDirectory() as tempdir:
|
|
780
585
|
with self.settings(GIT_ROOT=tempdir):
|
|
781
|
-
|
|
782
|
-
def create_empty_repo(path, url, clone_initially=False):
|
|
783
|
-
os.makedirs(path, exist_ok=True)
|
|
784
|
-
return mock.DEFAULT
|
|
785
|
-
|
|
786
|
-
MockGitRepo.side_effect = create_empty_repo
|
|
787
|
-
MockGitRepo.return_value.checkout.return_value = (self.COMMIT_HEXSHA, False)
|
|
788
|
-
|
|
789
586
|
self.mock_request.id = uuid.uuid4()
|
|
790
587
|
|
|
588
|
+
self.repo.branch = "valid-files"
|
|
589
|
+
self.repo.save()
|
|
791
590
|
job_model = GitRepositoryDryRun().job_model
|
|
792
591
|
job_result = run_job_for_testing(
|
|
793
592
|
job=job_model,
|
|
@@ -801,14 +600,33 @@ class GitTest(TransactionTestCase):
|
|
|
801
600
|
(job_result.traceback, list(job_result.job_log_entries.values_list("message", flat=True))),
|
|
802
601
|
)
|
|
803
602
|
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
603
|
+
log_entries = JobLogEntry.objects.filter(job_result=job_result)
|
|
604
|
+
|
|
605
|
+
try:
|
|
606
|
+
log_entries.get(message__contains="Addition - `__init__.py`")
|
|
607
|
+
log_entries.get(message__contains="Addition - `config_context_schemas/schema-1.yaml`")
|
|
608
|
+
log_entries.get(message__contains="Addition - `config_contexts/context.yaml`")
|
|
609
|
+
log_entries.get(message__contains="Addition - `config_contexts/devices/test-device.json`")
|
|
610
|
+
log_entries.get(message__contains="Addition - `config_contexts/locations/Test Location.json`")
|
|
611
|
+
log_entries.get(message__contains="Addition - `export_templates/dcim/device/template.j2`")
|
|
612
|
+
log_entries.get(message__contains="Addition - `export_templates/dcim/device/template2.html`")
|
|
613
|
+
log_entries.get(message__contains="Addition - `export_templates/ipam/vlan/template.j2`")
|
|
614
|
+
log_entries.get(message__contains="Addition - `jobs/__init__.py`")
|
|
615
|
+
log_entries.get(message__contains="Addition - `jobs/my_job.py`")
|
|
616
|
+
except JobLogEntry.DoesNotExist:
|
|
617
|
+
for log in log_entries:
|
|
618
|
+
print(log.message)
|
|
619
|
+
raise
|
|
620
|
+
|
|
621
|
+
self.assertFalse(ConfigContextSchema.objects.filter(owner_object_id=self.repo.pk).exists())
|
|
622
|
+
self.assertFalse(ConfigContext.objects.filter(owner_object_id=self.repo.pk).exists())
|
|
623
|
+
self.assertFalse(ExportTemplate.objects.filter(owner_object_id=self.repo.pk).exists())
|
|
624
|
+
self.assertFalse(Job.objects.filter(module_name__startswith=self.repo.slug).exists())
|
|
625
|
+
|
|
626
|
+
# TODO: test dry-run against a branch name
|
|
627
|
+
# TODO: test dry-run against a specific commit hash
|
|
810
628
|
|
|
811
|
-
def test_duplicate_repo_url_with_unique_provided_contents(self
|
|
629
|
+
def test_duplicate_repo_url_with_unique_provided_contents(self):
|
|
812
630
|
"""Create a duplicate repo but with unique provided_contents."""
|
|
813
631
|
remote_url = "http://localhost/duplicates.git"
|
|
814
632
|
repo1 = GitRepository(
|
|
@@ -828,7 +646,7 @@ class GitTest(TransactionTestCase):
|
|
|
828
646
|
repos = GitRepository.objects.filter(remote_url=remote_url)
|
|
829
647
|
self.assertEqual(repos.count(), 2)
|
|
830
648
|
|
|
831
|
-
def test_duplicate_repo_url_with_duplicate_provided_contents(self
|
|
649
|
+
def test_duplicate_repo_url_with_duplicate_provided_contents(self):
|
|
832
650
|
"""Create a duplicate repo but with duplicate provided_contents."""
|
|
833
651
|
remote_url = "http://localhost/duplicates.git"
|
|
834
652
|
repo1 = GitRepository(
|