nautobot 2.4.1__py3-none-any.whl → 2.4.2__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/circuits/tests/integration/test_circuits_bulk_operations.py +43 -0
- nautobot/circuits/tests/integration/test_relationships.py +1 -1
- nautobot/core/apps/__init__.py +0 -5
- nautobot/core/templates/generic/object_bulk_destroy.html +1 -1
- nautobot/core/testing/integration.py +437 -10
- nautobot/core/tests/test_jobs.py +34 -2
- nautobot/core/utils/git.py +7 -2
- nautobot/core/views/generic.py +1 -1
- nautobot/core/views/mixins.py +13 -6
- nautobot/core/views/utils.py +2 -2
- nautobot/dcim/forms.py +12 -0
- nautobot/dcim/tables/devices.py +2 -1
- nautobot/dcim/templates/dcim/cable.html +1 -1
- nautobot/dcim/templates/dcim/device/base.html +1 -1
- nautobot/dcim/templates/dcim/device.html +2 -2
- nautobot/dcim/templates/dcim/device_component.html +1 -1
- nautobot/dcim/templates/dcim/devicetype.html +1 -1
- nautobot/dcim/templates/dcim/location.html +1 -1
- nautobot/dcim/templates/dcim/locationtype.html +1 -1
- nautobot/dcim/templates/dcim/locationtype_retrieve.html +1 -1
- nautobot/dcim/templates/dcim/manufacturer.html +1 -1
- nautobot/dcim/templates/dcim/platform.html +1 -1
- nautobot/dcim/templates/dcim/powerfeed.html +1 -1
- nautobot/dcim/templates/dcim/powerpanel.html +1 -1
- nautobot/dcim/templates/dcim/rack.html +1 -1
- nautobot/dcim/templates/dcim/rackgroup.html +1 -1
- nautobot/dcim/templates/dcim/rackreservation.html +2 -2
- nautobot/dcim/templates/dcim/virtualchassis.html +1 -1
- nautobot/dcim/tests/integration/test_device_bulk_operations.py +30 -0
- nautobot/dcim/tests/integration/test_location_bulk_operations.py +43 -0
- nautobot/dcim/tests/test_views.py +9 -1
- nautobot/dcim/views.py +12 -15
- nautobot/extras/api/serializers.py +33 -0
- nautobot/extras/api/views.py +11 -3
- nautobot/extras/constants.py +1 -0
- nautobot/extras/datasources/git.py +125 -0
- nautobot/extras/migrations/0122_add_graphqlquery_owner_content_type.py +34 -0
- nautobot/extras/models/customfields.py +29 -12
- nautobot/extras/models/datasources.py +85 -0
- nautobot/extras/models/models.py +15 -0
- nautobot/extras/models/relationships.py +17 -5
- nautobot/extras/signals.py +15 -1
- nautobot/extras/templates/extras/computedfield.html +1 -1
- nautobot/extras/templates/extras/configcontext.html +1 -1
- nautobot/extras/templates/extras/configcontextschema.html +1 -1
- nautobot/extras/templates/extras/customfield.html +1 -1
- nautobot/extras/templates/extras/customlink.html +1 -1
- nautobot/extras/templates/extras/dynamicgroup.html +1 -1
- nautobot/extras/templates/extras/exporttemplate.html +1 -1
- nautobot/extras/templates/extras/gitrepository.html +1 -1
- nautobot/extras/templates/extras/graphqlquery.html +1 -1
- nautobot/extras/templates/extras/job_detail.html +1 -1
- nautobot/extras/templates/extras/jobbutton_retrieve.html +1 -1
- nautobot/extras/templates/extras/jobhook.html +1 -1
- nautobot/extras/templates/extras/jobresult.html +1 -1
- nautobot/extras/templates/extras/objectchange.html +1 -1
- nautobot/extras/templates/extras/plugin_detail.html +1 -1
- nautobot/extras/templates/extras/relationship.html +1 -63
- nautobot/extras/templates/extras/role_retrieve.html +1 -1
- nautobot/extras/templates/extras/scheduledjob.html +1 -1
- nautobot/extras/templates/extras/secret.html +1 -1
- nautobot/extras/templates/extras/secretsgroup.html +1 -1
- nautobot/extras/templates/extras/status.html +1 -1
- nautobot/extras/templates/extras/tag.html +1 -1
- nautobot/extras/templates/extras/webhook.html +1 -1
- nautobot/extras/tests/git_data/01-valid-files/graphql_queries/device_interfaces.gql +8 -0
- nautobot/extras/tests/git_data/01-valid-files/graphql_queries/device_names.gql +5 -0
- nautobot/extras/tests/git_data/02-invalid-files/graphql_queries/bad_device_names.gql +5 -0
- nautobot/extras/tests/git_helper.py +9 -1
- nautobot/extras/tests/integration/__init__.py +29 -16
- nautobot/extras/tests/test_api.py +6 -0
- nautobot/extras/tests/test_customfields.py +49 -51
- nautobot/extras/tests/test_datasources.py +27 -0
- nautobot/extras/tests/test_models.py +283 -0
- nautobot/extras/tests/test_utils.py +22 -1
- nautobot/extras/utils.py +17 -8
- nautobot/extras/views.py +55 -12
- nautobot/ipam/models.py +8 -2
- nautobot/ipam/tables.py +2 -2
- nautobot/ipam/templates/ipam/ipaddress.html +1 -1
- nautobot/ipam/templates/ipam/prefix.html +1 -1
- nautobot/ipam/templates/ipam/rir.html +1 -1
- nautobot/ipam/templates/ipam/routetarget.html +1 -1
- nautobot/ipam/templates/ipam/service.html +1 -1
- nautobot/ipam/templates/ipam/vlan.html +1 -1
- nautobot/ipam/templates/ipam/vlangroup.html +1 -1
- nautobot/ipam/templates/ipam/vrf.html +1 -1
- nautobot/ipam/tests/test_models.py +24 -0
- nautobot/ipam/tests/test_utils.py +41 -2
- nautobot/ipam/utils/__init__.py +18 -11
- nautobot/project-static/docs/404.html +87 -12
- nautobot/project-static/docs/apps/index.html +87 -12
- nautobot/project-static/docs/apps/nautobot-apps.html +87 -12
- nautobot/project-static/docs/assets/javascripts/{bundle.88dd0f4e.min.js → bundle.60a45f97.min.js} +1 -1
- nautobot/project-static/docs/assets/javascripts/{bundle.88dd0f4e.min.js.map → bundle.60a45f97.min.js.map} +1 -1
- nautobot/project-static/docs/assets/javascripts/workers/{search.6ce7567c.min.js → search.f8cc74c7.min.js} +1 -1
- nautobot/project-static/docs/assets/javascripts/workers/{search.6ce7567c.min.js.map → search.f8cc74c7.min.js.map} +1 -1
- nautobot/project-static/docs/assets/stylesheets/{main.6f8fc17f.min.css → main.a40c8224.min.css} +1 -1
- nautobot/project-static/docs/code-reference/nautobot/apps/__init__.html +87 -12
- nautobot/project-static/docs/code-reference/nautobot/apps/admin.html +87 -12
- nautobot/project-static/docs/code-reference/nautobot/apps/api.html +87 -12
- nautobot/project-static/docs/code-reference/nautobot/apps/change_logging.html +87 -12
- nautobot/project-static/docs/code-reference/nautobot/apps/choices.html +87 -12
- nautobot/project-static/docs/code-reference/nautobot/apps/config.html +87 -12
- nautobot/project-static/docs/code-reference/nautobot/apps/constants.html +87 -12
- nautobot/project-static/docs/code-reference/nautobot/apps/datasources.html +87 -12
- nautobot/project-static/docs/code-reference/nautobot/apps/events.html +87 -12
- nautobot/project-static/docs/code-reference/nautobot/apps/exceptions.html +87 -12
- nautobot/project-static/docs/code-reference/nautobot/apps/factory.html +87 -12
- nautobot/project-static/docs/code-reference/nautobot/apps/filters.html +87 -12
- nautobot/project-static/docs/code-reference/nautobot/apps/forms.html +87 -12
- nautobot/project-static/docs/code-reference/nautobot/apps/graphql.html +87 -12
- nautobot/project-static/docs/code-reference/nautobot/apps/jobs.html +87 -12
- nautobot/project-static/docs/code-reference/nautobot/apps/models.html +87 -12
- nautobot/project-static/docs/code-reference/nautobot/apps/querysets.html +87 -12
- nautobot/project-static/docs/code-reference/nautobot/apps/secrets.html +87 -12
- nautobot/project-static/docs/code-reference/nautobot/apps/tables.html +87 -12
- nautobot/project-static/docs/code-reference/nautobot/apps/testing.html +87 -12
- nautobot/project-static/docs/code-reference/nautobot/apps/ui.html +87 -12
- nautobot/project-static/docs/code-reference/nautobot/apps/urls.html +87 -12
- nautobot/project-static/docs/code-reference/nautobot/apps/utils.html +177 -20
- nautobot/project-static/docs/code-reference/nautobot/apps/views.html +114 -17
- nautobot/project-static/docs/development/apps/api/configuration-view.html +87 -12
- nautobot/project-static/docs/development/apps/api/database-backend-config.html +87 -12
- nautobot/project-static/docs/development/apps/api/models/django-admin.html +87 -12
- nautobot/project-static/docs/development/apps/api/models/global-search.html +87 -12
- nautobot/project-static/docs/development/apps/api/models/graphql.html +87 -12
- nautobot/project-static/docs/development/apps/api/models/index.html +87 -12
- nautobot/project-static/docs/development/apps/api/nautobot-app-config.html +87 -12
- nautobot/project-static/docs/development/apps/api/platform-features/custom-validators.html +87 -12
- nautobot/project-static/docs/development/apps/api/platform-features/filter-extensions.html +87 -12
- nautobot/project-static/docs/development/apps/api/platform-features/git-repository-content.html +87 -12
- nautobot/project-static/docs/development/apps/api/platform-features/index.html +87 -12
- nautobot/project-static/docs/development/apps/api/platform-features/jinja2-filters.html +87 -12
- nautobot/project-static/docs/development/apps/api/platform-features/jobs.html +87 -12
- nautobot/project-static/docs/development/apps/api/platform-features/populating-extensibility-features.html +87 -12
- nautobot/project-static/docs/development/apps/api/platform-features/secrets-providers.html +87 -12
- nautobot/project-static/docs/development/apps/api/platform-features/table-extensions.html +87 -12
- nautobot/project-static/docs/development/apps/api/platform-features/uniquely-identify-objects.html +87 -12
- nautobot/project-static/docs/development/apps/api/prometheus.html +87 -12
- nautobot/project-static/docs/development/apps/api/setup.html +87 -12
- nautobot/project-static/docs/development/apps/api/testing.html +87 -12
- nautobot/project-static/docs/development/apps/api/ui-extensions/banners.html +87 -12
- nautobot/project-static/docs/development/apps/api/ui-extensions/home-page.html +87 -12
- nautobot/project-static/docs/development/apps/api/ui-extensions/index.html +87 -12
- nautobot/project-static/docs/development/apps/api/ui-extensions/navigation.html +87 -12
- nautobot/project-static/docs/development/apps/api/ui-extensions/object-views.html +87 -12
- nautobot/project-static/docs/development/apps/api/views/base-template.html +87 -12
- nautobot/project-static/docs/development/apps/api/views/core-view-overrides.html +87 -12
- nautobot/project-static/docs/development/apps/api/views/django-generic-views.html +87 -12
- nautobot/project-static/docs/development/apps/api/views/help-documentation.html +87 -12
- nautobot/project-static/docs/development/apps/api/views/index.html +87 -12
- nautobot/project-static/docs/development/apps/api/views/nautobot-generic-views.html +87 -12
- nautobot/project-static/docs/development/apps/api/views/nautobotuiviewset.html +87 -12
- nautobot/project-static/docs/development/apps/api/views/nautobotuiviewsetrouter.html +87 -12
- nautobot/project-static/docs/development/apps/api/views/notes.html +87 -12
- nautobot/project-static/docs/development/apps/api/views/rest-api.html +87 -12
- nautobot/project-static/docs/development/apps/api/views/urls.html +87 -12
- nautobot/project-static/docs/development/apps/index.html +87 -12
- nautobot/project-static/docs/development/apps/migration/code-updates.html +87 -12
- nautobot/project-static/docs/development/apps/migration/dependency-updates.html +87 -12
- nautobot/project-static/docs/development/apps/migration/from-v1.html +87 -12
- nautobot/project-static/docs/development/apps/migration/model-updates/dcim.html +87 -12
- nautobot/project-static/docs/development/apps/migration/model-updates/extras.html +87 -12
- nautobot/project-static/docs/development/apps/migration/model-updates/global.html +87 -12
- nautobot/project-static/docs/development/apps/migration/model-updates/ipam.html +87 -12
- nautobot/project-static/docs/development/apps/migration/ui-component-framework/best-practices.html +87 -12
- nautobot/project-static/docs/development/apps/migration/ui-component-framework/custom-content.html +87 -12
- nautobot/project-static/docs/development/apps/migration/ui-component-framework/index.html +88 -13
- nautobot/project-static/docs/development/apps/migration/ui-component-framework/migration-steps.html +87 -12
- nautobot/project-static/docs/development/apps/porting-from-netbox.html +87 -12
- nautobot/project-static/docs/development/core/application-registry.html +87 -12
- nautobot/project-static/docs/development/core/best-practices.html +87 -12
- nautobot/project-static/docs/development/core/bootstrap-ui.html +87 -12
- nautobot/project-static/docs/development/core/caching.html +87 -12
- nautobot/project-static/docs/development/core/controllers.html +87 -12
- nautobot/project-static/docs/development/core/docker-compose-advanced-use-cases.html +87 -12
- nautobot/project-static/docs/development/core/generic-views.html +87 -12
- nautobot/project-static/docs/development/core/getting-started.html +87 -12
- nautobot/project-static/docs/development/core/homepage.html +87 -12
- nautobot/project-static/docs/development/core/index.html +87 -12
- nautobot/project-static/docs/development/core/minikube-dev-environment-for-k8s-jobs.html +87 -12
- nautobot/project-static/docs/development/core/model-checklist.html +87 -12
- nautobot/project-static/docs/development/core/model-features.html +87 -12
- nautobot/project-static/docs/development/core/natural-keys.html +87 -12
- nautobot/project-static/docs/development/core/navigation-menu.html +87 -12
- nautobot/project-static/docs/development/core/release-checklist.html +87 -12
- nautobot/project-static/docs/development/core/role-internals.html +87 -12
- nautobot/project-static/docs/development/core/settings.html +87 -12
- nautobot/project-static/docs/development/core/style-guide.html +87 -12
- nautobot/project-static/docs/development/core/templates.html +88 -13
- nautobot/project-static/docs/development/core/testing.html +87 -12
- nautobot/project-static/docs/development/core/ui-component-framework.html +87 -12
- nautobot/project-static/docs/development/core/user-preferences.html +87 -12
- nautobot/project-static/docs/development/index.html +87 -12
- nautobot/project-static/docs/development/jobs/index.html +87 -12
- nautobot/project-static/docs/development/jobs/migration/from-v1.html +87 -12
- nautobot/project-static/docs/index.html +87 -12
- nautobot/project-static/docs/overview/application_stack.html +87 -12
- nautobot/project-static/docs/overview/design_philosophy.html +87 -12
- nautobot/project-static/docs/release-notes/index.html +87 -12
- nautobot/project-static/docs/release-notes/version-1.0.html +87 -12
- nautobot/project-static/docs/release-notes/version-1.1.html +87 -12
- nautobot/project-static/docs/release-notes/version-1.2.html +87 -12
- nautobot/project-static/docs/release-notes/version-1.3.html +87 -12
- nautobot/project-static/docs/release-notes/version-1.4.html +87 -12
- nautobot/project-static/docs/release-notes/version-1.5.html +87 -12
- nautobot/project-static/docs/release-notes/version-1.6.html +87 -12
- nautobot/project-static/docs/release-notes/version-2.0.html +87 -12
- nautobot/project-static/docs/release-notes/version-2.1.html +87 -12
- nautobot/project-static/docs/release-notes/version-2.2.html +87 -12
- nautobot/project-static/docs/release-notes/version-2.3.html +87 -12
- nautobot/project-static/docs/release-notes/version-2.4.html +277 -12
- nautobot/project-static/docs/requirements.txt +1 -1
- nautobot/project-static/docs/search/search_index.json +1 -1
- nautobot/project-static/docs/sitemap.xml +296 -288
- nautobot/project-static/docs/sitemap.xml.gz +0 -0
- nautobot/project-static/docs/user-guide/administration/configuration/authentication/ldap.html +87 -12
- nautobot/project-static/docs/user-guide/administration/configuration/authentication/remote.html +87 -12
- nautobot/project-static/docs/user-guide/administration/configuration/authentication/sso.html +87 -12
- nautobot/project-static/docs/user-guide/administration/configuration/index.html +87 -12
- nautobot/project-static/docs/user-guide/administration/configuration/redis.html +87 -12
- nautobot/project-static/docs/user-guide/administration/configuration/settings.html +87 -12
- nautobot/project-static/docs/user-guide/administration/configuration/time-zones.html +87 -12
- nautobot/project-static/docs/user-guide/administration/guides/celery-queues.html +90 -15
- nautobot/project-static/docs/user-guide/administration/guides/docker.html +87 -12
- nautobot/project-static/docs/user-guide/administration/guides/health-checks.html +87 -12
- nautobot/project-static/docs/user-guide/administration/guides/permissions.html +87 -12
- nautobot/project-static/docs/user-guide/administration/guides/prometheus-metrics.html +87 -12
- nautobot/project-static/docs/user-guide/administration/guides/replicating-nautobot.html +87 -12
- nautobot/project-static/docs/user-guide/administration/guides/request-profiling.html +87 -12
- nautobot/project-static/docs/user-guide/administration/guides/s3-django-storage.html +87 -12
- nautobot/project-static/docs/user-guide/administration/guides/selinux-troubleshooting.html +87 -12
- nautobot/project-static/docs/user-guide/administration/installation/app-install.html +87 -12
- nautobot/project-static/docs/user-guide/administration/installation/external-authentication.html +87 -12
- nautobot/project-static/docs/user-guide/administration/installation/http-server.html +87 -12
- nautobot/project-static/docs/user-guide/administration/installation/index.html +87 -12
- nautobot/project-static/docs/user-guide/administration/installation/install_system.html +87 -12
- nautobot/project-static/docs/user-guide/administration/installation/nautobot.html +87 -12
- nautobot/project-static/docs/user-guide/administration/installation/services.html +87 -12
- nautobot/project-static/docs/user-guide/administration/migration/migrating-from-netbox.html +87 -12
- nautobot/project-static/docs/user-guide/administration/migration/migrating-from-postgresql.html +87 -12
- nautobot/project-static/docs/user-guide/administration/security/index.html +9420 -0
- nautobot/project-static/docs/user-guide/administration/security/notices.html +9843 -0
- nautobot/project-static/docs/user-guide/administration/tools/nautobot-server.html +87 -12
- nautobot/project-static/docs/user-guide/administration/tools/nautobot-shell.html +87 -12
- nautobot/project-static/docs/user-guide/administration/upgrading/database-backup.html +87 -12
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/after-you-upgrade.html +87 -12
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/before-you-upgrade.html +87 -12
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/for-developers.html +87 -12
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/index.html +87 -12
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/whats-changed.html +87 -12
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/region-and-site-data-migration-guide.html +87 -12
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/upgrading-from-nautobot-v1.html +87 -12
- nautobot/project-static/docs/user-guide/administration/upgrading/upgrading.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/circuits/circuit.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/circuits/circuittermination.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/circuits/circuittype.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/circuits/provider.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/circuits/providernetwork.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloud.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudaccount.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudnetwork.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudnetworkprefixassignment.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudresourcetype.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudservice.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudservicenetworkassignment.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/dcim/cable.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleport.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleporttemplate.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleserverport.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleserverporttemplate.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/dcim/controller.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/dcim/controllermanageddevicegroup.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/dcim/device.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/dcim/devicebay.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/dcim/devicebaytemplate.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/dcim/devicefamily.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/dcim/deviceredundancygroup.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/dcim/devicetype.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/dcim/frontport.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/dcim/frontporttemplate.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/dcim/interface.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/dcim/interfaceredundancygroup.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/dcim/interfacetemplate.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/dcim/inventoryitem.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/dcim/location.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/dcim/locationtype.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/dcim/manufacturer.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/dcim/module.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/dcim/modulebay.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/dcim/modulebaytemplate.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/dcim/moduletype.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/dcim/platform.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/dcim/powerfeed.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/dcim/poweroutlet.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/dcim/poweroutlettemplate.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/dcim/powerpanel.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/dcim/powerport.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/dcim/powerporttemplate.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rack.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rackgroup.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rackreservation.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rearport.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rearporttemplate.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/dcim/softwareimagefile.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/dcim/softwareversion.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/dcim/virtualchassis.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/dcim/virtualdevicecontext.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/extras/configcontext.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/extras/configcontextschema.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/extras/contact.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/extras/team.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/ipam/ipaddress.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/ipam/namespace.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/ipam/prefix.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/ipam/rir.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/ipam/routetarget.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/ipam/service.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/ipam/vlan.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/ipam/vlangroup.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/ipam/vrf.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/overview/introduction.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/tenancy/tenant.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/tenancy/tenantgroup.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/cluster.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/clustergroup.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/clustertype.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/virtualmachine.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/vminterface.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/wireless/index.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/wireless/radioprofile.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/wireless/supporteddatarate.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/wireless/wirelessnetwork.html +87 -12
- nautobot/project-static/docs/user-guide/feature-guides/contacts-and-teams.html +87 -12
- nautobot/project-static/docs/user-guide/feature-guides/custom-fields.html +87 -12
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/creating-devices.html +87 -12
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/creating-location-types-and-locations.html +87 -12
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/index.html +90 -15
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/interfaces.html +87 -12
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/ipam.html +87 -12
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/platforms.html +87 -12
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/search-bar.html +87 -12
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/tenants.html +87 -12
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/vlans-and-vlan-groups.html +87 -12
- nautobot/project-static/docs/user-guide/feature-guides/git-data-source.html +187 -29
- nautobot/project-static/docs/user-guide/feature-guides/graphql.html +87 -12
- nautobot/project-static/docs/user-guide/feature-guides/ip-address-merge-tool.html +87 -12
- nautobot/project-static/docs/user-guide/feature-guides/relationships.html +87 -12
- nautobot/project-static/docs/user-guide/feature-guides/software-image-files-and-versions.html +87 -12
- nautobot/project-static/docs/user-guide/feature-guides/wireless-networks-and-controllers.html +87 -12
- nautobot/project-static/docs/user-guide/index.html +87 -12
- nautobot/project-static/docs/user-guide/platform-functionality/change-logging.html +87 -12
- nautobot/project-static/docs/user-guide/platform-functionality/computedfield.html +87 -12
- nautobot/project-static/docs/user-guide/platform-functionality/customfield.html +87 -12
- nautobot/project-static/docs/user-guide/platform-functionality/customlink.html +87 -12
- nautobot/project-static/docs/user-guide/platform-functionality/dynamicgroup.html +87 -12
- nautobot/project-static/docs/user-guide/platform-functionality/events.html +87 -12
- nautobot/project-static/docs/user-guide/platform-functionality/exporttemplate.html +87 -12
- nautobot/project-static/docs/user-guide/platform-functionality/externalintegration.html +87 -12
- nautobot/project-static/docs/user-guide/platform-functionality/gitrepository.html +407 -14
- nautobot/project-static/docs/user-guide/platform-functionality/graphql.html +87 -12
- nautobot/project-static/docs/user-guide/platform-functionality/graphqlquery.html +87 -12
- nautobot/project-static/docs/user-guide/platform-functionality/imageattachment.html +87 -12
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/index.html +87 -12
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/job-scheduling-and-approvals.html +87 -12
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobbutton.html +87 -12
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobhook.html +87 -12
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobqueue.html +87 -12
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/kubernetes-job-support.html +87 -12
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/models.html +87 -12
- nautobot/project-static/docs/user-guide/platform-functionality/napalm.html +87 -12
- nautobot/project-static/docs/user-guide/platform-functionality/note.html +87 -12
- nautobot/project-static/docs/user-guide/platform-functionality/objectmetadata.html +87 -12
- nautobot/project-static/docs/user-guide/platform-functionality/relationship.html +87 -12
- nautobot/project-static/docs/user-guide/platform-functionality/rendering-jinja-templates.html +87 -12
- nautobot/project-static/docs/user-guide/platform-functionality/rest-api/authentication.html +87 -12
- nautobot/project-static/docs/user-guide/platform-functionality/rest-api/filtering.html +87 -12
- nautobot/project-static/docs/user-guide/platform-functionality/rest-api/overview.html +87 -12
- nautobot/project-static/docs/user-guide/platform-functionality/rest-api/ui-related-endpoints.html +87 -12
- nautobot/project-static/docs/user-guide/platform-functionality/role.html +87 -12
- nautobot/project-static/docs/user-guide/platform-functionality/savedview.html +87 -12
- nautobot/project-static/docs/user-guide/platform-functionality/secret.html +87 -12
- nautobot/project-static/docs/user-guide/platform-functionality/staticgroupassociation.html +87 -12
- nautobot/project-static/docs/user-guide/platform-functionality/status.html +87 -12
- nautobot/project-static/docs/user-guide/platform-functionality/tag.html +87 -12
- nautobot/project-static/docs/user-guide/platform-functionality/template-filters.html +87 -12
- nautobot/project-static/docs/user-guide/platform-functionality/users/objectpermission.html +87 -12
- nautobot/project-static/docs/user-guide/platform-functionality/users/token.html +87 -12
- nautobot/project-static/docs/user-guide/platform-functionality/webhook.html +87 -12
- nautobot/tenancy/templates/tenancy/tenant.html +1 -2
- nautobot/tenancy/templates/tenancy/tenantgroup.html +1 -1
- nautobot/virtualization/templates/virtualization/cluster.html +1 -1
- nautobot/virtualization/templates/virtualization/clustergroup.html +1 -1
- nautobot/virtualization/templates/virtualization/clustertype.html +1 -1
- nautobot/virtualization/templates/virtualization/virtualmachine.html +1 -1
- nautobot/virtualization/templates/virtualization/vminterface.html +1 -1
- {nautobot-2.4.1.dist-info → nautobot-2.4.2.dist-info}/METADATA +5 -5
- {nautobot-2.4.1.dist-info → nautobot-2.4.2.dist-info}/RECORD +404 -397
- nautobot/dcim/tests/integration/test_device_bulk_delete.py +0 -189
- nautobot/dcim/tests/integration/test_device_bulk_edit.py +0 -181
- /nautobot/project-static/docs/assets/stylesheets/{main.6f8fc17f.min.css.map → main.a40c8224.min.css.map} +0 -0
- {nautobot-2.4.1.dist-info → nautobot-2.4.2.dist-info}/LICENSE.txt +0 -0
- {nautobot-2.4.1.dist-info → nautobot-2.4.2.dist-info}/NOTICE +0 -0
- {nautobot-2.4.1.dist-info → nautobot-2.4.2.dist-info}/WHEEL +0 -0
- {nautobot-2.4.1.dist-info → nautobot-2.4.2.dist-info}/entry_points.txt +0 -0
|
@@ -1207,6 +1207,10 @@ class GitRepositoryTest(APIViewTestCases.APIViewTestCase):
|
|
|
1207
1207
|
url = reverse("extras-api:gitrepository-sync", kwargs={"pk": self.repos[0].id})
|
|
1208
1208
|
response = self.client.post(url, format="json", **self.header)
|
|
1209
1209
|
self.assertHttpStatus(response, status.HTTP_200_OK)
|
|
1210
|
+
self.assertIn("message", response.data)
|
|
1211
|
+
self.assertIn("job_result", response.data)
|
|
1212
|
+
self.assertEqual(response.data["message"], f"Repository {self.repos[0].name} sync job added to queue.")
|
|
1213
|
+
self.assertIsInstance(response.data["job_result"], dict)
|
|
1210
1214
|
|
|
1211
1215
|
def test_create_with_app_provided_contents(self):
|
|
1212
1216
|
"""Test that `provided_contents` published by an App works."""
|
|
@@ -1241,6 +1245,8 @@ class GraphQLQueryTest(APIViewTestCases.APIViewTestCase):
|
|
|
1241
1245
|
},
|
|
1242
1246
|
]
|
|
1243
1247
|
|
|
1248
|
+
choices_fields = ["owner_content_type"]
|
|
1249
|
+
|
|
1244
1250
|
@classmethod
|
|
1245
1251
|
def setUpTestData(cls):
|
|
1246
1252
|
cls.graphqlqueries = (
|
|
@@ -53,12 +53,12 @@ class CustomFieldTest(ModelTestCases.BaseModelTestCase, TestCase):
|
|
|
53
53
|
|
|
54
54
|
instance.refresh_from_db()
|
|
55
55
|
instance.key = "custom_field_2"
|
|
56
|
-
with self.
|
|
56
|
+
with self.assertRaisesRegex(ValidationError, "Key cannot be changed once created"):
|
|
57
57
|
instance.validated_save()
|
|
58
58
|
|
|
59
59
|
instance.refresh_from_db()
|
|
60
60
|
instance.type = CustomFieldTypeChoices.TYPE_SELECT
|
|
61
|
-
with self.
|
|
61
|
+
with self.assertRaisesRegex(ValidationError, "Type cannot be changed once created"):
|
|
62
62
|
instance.validated_save()
|
|
63
63
|
|
|
64
64
|
def test_simple_fields(self):
|
|
@@ -165,9 +165,14 @@ class CustomFieldTest(ModelTestCases.BaseModelTestCase, TestCase):
|
|
|
165
165
|
cf.save()
|
|
166
166
|
cf.content_types.set([obj_type])
|
|
167
167
|
|
|
168
|
-
CustomFieldChoice.objects.create(custom_field=cf, value="Option A")
|
|
169
|
-
|
|
170
|
-
CustomFieldChoice.objects.create(custom_field=cf, value="Option
|
|
168
|
+
CustomFieldChoice.objects.create(custom_field=cf, value="Option A", weight=100)
|
|
169
|
+
self.assertEqual(["Option A"], cf.choices)
|
|
170
|
+
CustomFieldChoice.objects.create(custom_field=cf, value="Option B", weight=200)
|
|
171
|
+
self.assertEqual(["Option A", "Option B"], cf.choices)
|
|
172
|
+
CustomFieldChoice.objects.create(custom_field=cf, value="Option C", weight=300)
|
|
173
|
+
self.assertEqual(["Option A", "Option B", "Option C"], cf.choices)
|
|
174
|
+
with self.assertNumQueries(0): # verify caching
|
|
175
|
+
self.assertEqual(["Option A", "Option B", "Option C"], cf.choices)
|
|
171
176
|
|
|
172
177
|
# Assign a value to the first Location
|
|
173
178
|
location = Location.objects.get(name="Location A")
|
|
@@ -199,9 +204,14 @@ class CustomFieldTest(ModelTestCases.BaseModelTestCase, TestCase):
|
|
|
199
204
|
cf.save()
|
|
200
205
|
cf.content_types.set([obj_type])
|
|
201
206
|
|
|
202
|
-
CustomFieldChoice.objects.create(custom_field=cf, value="Option A")
|
|
203
|
-
|
|
204
|
-
CustomFieldChoice.objects.create(custom_field=cf, value="Option
|
|
207
|
+
CustomFieldChoice.objects.create(custom_field=cf, value="Option A", weight=100)
|
|
208
|
+
self.assertEqual(["Option A"], cf.choices)
|
|
209
|
+
CustomFieldChoice.objects.create(custom_field=cf, value="Option B", weight=200)
|
|
210
|
+
self.assertEqual(["Option A", "Option B"], cf.choices)
|
|
211
|
+
CustomFieldChoice.objects.create(custom_field=cf, value="Option C", weight=300)
|
|
212
|
+
self.assertEqual(["Option A", "Option B", "Option C"], cf.choices)
|
|
213
|
+
with self.assertNumQueries(0): # verify caching
|
|
214
|
+
self.assertEqual(["Option A", "Option B", "Option C"], cf.choices)
|
|
205
215
|
|
|
206
216
|
# Assign a value to the first Location
|
|
207
217
|
location = Location.objects.get(name="Location A")
|
|
@@ -293,21 +303,18 @@ class CustomFieldTest(ModelTestCases.BaseModelTestCase, TestCase):
|
|
|
293
303
|
# Assign a disallowed value (list) to the first Location
|
|
294
304
|
location = Location.objects.get(name="Location A")
|
|
295
305
|
location.cf[cf.key] = ["I", "am", "a", "list"]
|
|
296
|
-
with self.
|
|
306
|
+
with self.assertRaisesRegex(ValidationError, "Value must be a string"):
|
|
297
307
|
location.validated_save()
|
|
298
|
-
self.assertIn("Value must be a string", str(context.exception))
|
|
299
308
|
|
|
300
309
|
# Assign another disallowed value (int) to the first Location
|
|
301
310
|
location.cf[cf.key] = 2
|
|
302
|
-
with self.
|
|
311
|
+
with self.assertRaisesRegex(ValidationError, "Value must be a string"):
|
|
303
312
|
location.validated_save()
|
|
304
|
-
self.assertIn("Value must be a string", str(context.exception))
|
|
305
313
|
|
|
306
314
|
# Assign another disallowed value (bool) to the first Location
|
|
307
315
|
location.cf[cf.key] = True
|
|
308
|
-
with self.
|
|
316
|
+
with self.assertRaisesRegex(ValidationError, "Value must be a string"):
|
|
309
317
|
location.validated_save()
|
|
310
|
-
self.assertIn("Value must be a string", str(context.exception))
|
|
311
318
|
|
|
312
319
|
# Delete the stored value
|
|
313
320
|
location.cf.pop(cf.key)
|
|
@@ -1294,7 +1301,7 @@ class CustomFieldModelTest(TestCase):
|
|
|
1294
1301
|
custom_field.content_types.set([ContentType.objects.get_for_model(Provider)])
|
|
1295
1302
|
|
|
1296
1303
|
provider = Provider.objects.create(name="Test")
|
|
1297
|
-
with self.
|
|
1304
|
+
with self.assertRaisesRegex(ValidationError, "Missing required custom field 'custom_field'"):
|
|
1298
1305
|
provider.validated_save()
|
|
1299
1306
|
|
|
1300
1307
|
def test_custom_field_required_on_update(self):
|
|
@@ -1312,7 +1319,7 @@ class CustomFieldModelTest(TestCase):
|
|
|
1312
1319
|
provider = Provider.objects.create(name="Test", _custom_field_data={"custom_field": "Value"})
|
|
1313
1320
|
provider.validated_save()
|
|
1314
1321
|
provider._custom_field_data.pop("custom_field")
|
|
1315
|
-
with self.
|
|
1322
|
+
with self.assertRaisesRegex(ValidationError, "Missing required custom field 'custom_field'"):
|
|
1316
1323
|
provider.validated_save()
|
|
1317
1324
|
|
|
1318
1325
|
def test_update_removed_custom_field(self):
|
|
@@ -1373,7 +1380,7 @@ class CustomFieldModelTest(TestCase):
|
|
|
1373
1380
|
"""
|
|
1374
1381
|
Check that a ValidationError is raised if any required custom fields are not present.
|
|
1375
1382
|
"""
|
|
1376
|
-
cf3 = CustomField(type=CustomFieldTypeChoices.TYPE_TEXT, label="Baz", required=True)
|
|
1383
|
+
cf3 = CustomField(key="baz", type=CustomFieldTypeChoices.TYPE_TEXT, label="Baz", required=True)
|
|
1377
1384
|
cf3.save()
|
|
1378
1385
|
cf3.content_types.set([ContentType.objects.get_for_model(Location)])
|
|
1379
1386
|
|
|
@@ -1381,7 +1388,7 @@ class CustomFieldModelTest(TestCase):
|
|
|
1381
1388
|
|
|
1382
1389
|
# Set custom field data with a required field omitted
|
|
1383
1390
|
location.cf["foo"] = "abc"
|
|
1384
|
-
with self.
|
|
1391
|
+
with self.assertRaisesRegex(ValidationError, "Missing required custom field 'baz'"):
|
|
1385
1392
|
location.clean()
|
|
1386
1393
|
|
|
1387
1394
|
location.cf["baz"] = "def"
|
|
@@ -1427,38 +1434,20 @@ class CustomFieldModelTest(TestCase):
|
|
|
1427
1434
|
"""
|
|
1428
1435
|
Check the GraphQL validation method on CustomField Key Attribute.
|
|
1429
1436
|
"""
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
"
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
str(error.exception),
|
|
1445
|
-
)
|
|
1446
|
-
# Check if it catches the cf.key with hyphens.
|
|
1447
|
-
cf1.key = "test-1-custom-field"
|
|
1448
|
-
with self.assertRaises(ValidationError) as error:
|
|
1449
|
-
cf1.validated_save()
|
|
1450
|
-
self.assertIn(
|
|
1451
|
-
"This key is not Python/GraphQL safe. Please do not start the key with a digit and do not use hyphens or whitespace",
|
|
1452
|
-
str(error.exception),
|
|
1453
|
-
)
|
|
1454
|
-
# Check if it catches the cf.key with special characters
|
|
1455
|
-
cf1.key = "test_1_custom_f)(&d"
|
|
1456
|
-
with self.assertRaises(ValidationError) as error:
|
|
1457
|
-
cf1.validated_save()
|
|
1458
|
-
self.assertIn(
|
|
1459
|
-
"This key is not Python/GraphQL safe. Please do not start the key with a digit and do not use hyphens or whitespace",
|
|
1460
|
-
str(error.exception),
|
|
1461
|
-
)
|
|
1437
|
+
cf1 = CustomField(type=CustomFieldTypeChoices.TYPE_TEXT, label="Test 1")
|
|
1438
|
+
for invalid_key in [
|
|
1439
|
+
"12_test_1", # Check if it catches the cf.key starting with a digit.
|
|
1440
|
+
"test 1", # Check if it catches the cf.key with whitespace.
|
|
1441
|
+
"test-1-custom-field", # Check if it catches the cf.key with hyphens.
|
|
1442
|
+
"test_1_custom_f)(&d", # Check if it catches the cf.key with special characters
|
|
1443
|
+
]:
|
|
1444
|
+
with self.assertRaisesRegex(
|
|
1445
|
+
ValidationError,
|
|
1446
|
+
"This key is not Python/GraphQL safe. "
|
|
1447
|
+
"Please do not start the key with a digit and do not use hyphens or whitespace",
|
|
1448
|
+
):
|
|
1449
|
+
cf1.key = invalid_key
|
|
1450
|
+
cf1.validated_save()
|
|
1462
1451
|
|
|
1463
1452
|
|
|
1464
1453
|
class CustomFieldFilterTest(TestCase):
|
|
@@ -1934,9 +1923,11 @@ class CustomFieldChoiceTest(ModelTestCases.BaseModelTestCase):
|
|
|
1934
1923
|
)
|
|
1935
1924
|
self.cf.save()
|
|
1936
1925
|
self.cf.content_types.set([obj_type])
|
|
1926
|
+
self.assertEqual(self.cf.choices, [])
|
|
1937
1927
|
|
|
1938
1928
|
self.choice = CustomFieldChoice(custom_field=self.cf, value="Foo")
|
|
1939
1929
|
self.choice.save()
|
|
1930
|
+
self.assertEqual(self.cf.choices, ["Foo"])
|
|
1940
1931
|
|
|
1941
1932
|
location_status = Status.objects.get_for_model(Location).first()
|
|
1942
1933
|
self.location_type = LocationType.objects.get(name="Campus")
|
|
@@ -1952,7 +1943,7 @@ class CustomFieldChoiceTest(ModelTestCases.BaseModelTestCase):
|
|
|
1952
1943
|
|
|
1953
1944
|
def test_default_value_must_be_valid_choice_sad_path(self):
|
|
1954
1945
|
self.cf.default = "invalid value"
|
|
1955
|
-
with self.
|
|
1946
|
+
with self.assertRaisesRegex(ValidationError, 'Invalid default value "invalid value"'):
|
|
1956
1947
|
self.cf.full_clean()
|
|
1957
1948
|
|
|
1958
1949
|
def test_default_value_must_be_valid_choice_happy_path(self):
|
|
@@ -1965,6 +1956,13 @@ class CustomFieldChoiceTest(ModelTestCases.BaseModelTestCase):
|
|
|
1965
1956
|
with self.assertRaises(ProtectedError):
|
|
1966
1957
|
self.choice.delete()
|
|
1967
1958
|
|
|
1959
|
+
def test_inactive_choice_can_be_deleted(self):
|
|
1960
|
+
self.location._custom_field_data.pop("cf1")
|
|
1961
|
+
self.location.validated_save()
|
|
1962
|
+
self.assertEqual(self.cf.choices, ["Foo"])
|
|
1963
|
+
self.choice.delete()
|
|
1964
|
+
self.assertEqual(self.cf.choices, [])
|
|
1965
|
+
|
|
1968
1966
|
def test_custom_choice_deleted_with_field(self):
|
|
1969
1967
|
self.cf.delete()
|
|
1970
1968
|
self.assertEqual(CustomField.objects.count(), 1) # custom field automatically added by the Example App
|
|
@@ -30,6 +30,7 @@ from nautobot.extras.models import (
|
|
|
30
30
|
ConfigContextSchema,
|
|
31
31
|
ExportTemplate,
|
|
32
32
|
GitRepository,
|
|
33
|
+
GraphQLQuery,
|
|
33
34
|
Job,
|
|
34
35
|
JobButton,
|
|
35
36
|
JobHook,
|
|
@@ -197,6 +198,15 @@ class GitTest(TransactionTestCase):
|
|
|
197
198
|
)
|
|
198
199
|
self.assertIsNotNone(export_template_vlan)
|
|
199
200
|
|
|
201
|
+
def assert_graphql_query_exists(self, name="device_names.gql"):
|
|
202
|
+
"""Helper function to assert Graphql query exists."""
|
|
203
|
+
graphql_query = GraphQLQuery.objects.get(
|
|
204
|
+
name=name,
|
|
205
|
+
owner_object_id=self.repo.pk,
|
|
206
|
+
owner_content_type=ContentType.objects.get_for_model(GitRepository),
|
|
207
|
+
)
|
|
208
|
+
self.assertIsNotNone(graphql_query)
|
|
209
|
+
|
|
200
210
|
def assert_job_exists(self, name="MyJob", installed=True):
|
|
201
211
|
"""Helper function to assert JobModel and registered Job exist."""
|
|
202
212
|
# Is it registered correctly in the database?
|
|
@@ -348,6 +358,10 @@ class GitTest(TransactionTestCase):
|
|
|
348
358
|
# Case when ContentType.model != ContentType.name, template was added and deleted during sync (#570)
|
|
349
359
|
self.assert_export_template_vlan_exists("template.j2")
|
|
350
360
|
|
|
361
|
+
# Make sure Graphgl queries were loaded
|
|
362
|
+
self.assert_graphql_query_exists("device_names")
|
|
363
|
+
self.assert_graphql_query_exists("device_interfaces")
|
|
364
|
+
|
|
351
365
|
# Make sure Jobs were successfully loaded from file and registered as JobModels
|
|
352
366
|
self.assert_job_exists(name="MyJob")
|
|
353
367
|
self.assert_job_exists(name="MyJobButtonReceiver")
|
|
@@ -439,6 +453,7 @@ class GitTest(TransactionTestCase):
|
|
|
439
453
|
self.assertFalse(ConfigContextSchema.objects.filter(owner_object_id=self.repo.id).exists())
|
|
440
454
|
self.assertFalse(ConfigContext.objects.filter(owner_object_id=self.repo.id).exists())
|
|
441
455
|
self.assertFalse(ExportTemplate.objects.filter(owner_object_id=self.repo.id).exists())
|
|
456
|
+
self.assertFalse(GraphQLQuery.objects.filter(owner_object_id=self.repo.id).exists())
|
|
442
457
|
self.assertFalse(Job.objects.filter(module_name__startswith=f"{self.repo.slug}.").exists())
|
|
443
458
|
device = Device.objects.get(name=self.device.name)
|
|
444
459
|
self.assertIsNone(device.local_config_context_data)
|
|
@@ -505,6 +520,11 @@ class GitTest(TransactionTestCase):
|
|
|
505
520
|
grouping="jobs",
|
|
506
521
|
message__contains="Error in loading Jobs from Git repository: ",
|
|
507
522
|
)
|
|
523
|
+
failure_logs.get(
|
|
524
|
+
grouping="graphql queries",
|
|
525
|
+
message__contains="Error processing GraphQL query file 'bad_device_names.gql': Syntax Error GraphQL (4:5) Expected Name, found }",
|
|
526
|
+
)
|
|
527
|
+
|
|
508
528
|
except (AssertionError, JobLogEntry.DoesNotExist):
|
|
509
529
|
for log in log_entries:
|
|
510
530
|
print(log.message)
|
|
@@ -630,6 +650,8 @@ class GitTest(TransactionTestCase):
|
|
|
630
650
|
self.assert_export_template_device("template.j2")
|
|
631
651
|
self.assert_export_template_html_exist("template2.html")
|
|
632
652
|
self.assert_export_template_vlan_exists("template.j2")
|
|
653
|
+
self.assert_graphql_query_exists(name="device_names")
|
|
654
|
+
self.assert_graphql_query_exists(name="device_interfaces")
|
|
633
655
|
self.assert_job_exists(name="MyJob")
|
|
634
656
|
self.assert_job_exists(name="MyJobButtonReceiver")
|
|
635
657
|
self.assert_job_exists(name="MyJobHookReceiver")
|
|
@@ -669,6 +691,8 @@ class GitTest(TransactionTestCase):
|
|
|
669
691
|
self.assert_export_template_device("template.j2")
|
|
670
692
|
self.assert_export_template_html_exist("template2.html")
|
|
671
693
|
self.assert_export_template_vlan_exists("template.j2")
|
|
694
|
+
self.assert_graphql_query_exists("device_names")
|
|
695
|
+
self.assert_graphql_query_exists("device_interfaces")
|
|
672
696
|
self.assert_job_exists(name="MyJob")
|
|
673
697
|
self.assert_job_exists(name="MyJobButtonReceiver")
|
|
674
698
|
self.assert_job_exists(name="MyJobHookReceiver")
|
|
@@ -711,6 +735,8 @@ class GitTest(TransactionTestCase):
|
|
|
711
735
|
log_entries.get(message__contains="Addition - `export_templates/dcim/device/template.j2`")
|
|
712
736
|
log_entries.get(message__contains="Addition - `export_templates/dcim/device/template2.html`")
|
|
713
737
|
log_entries.get(message__contains="Addition - `export_templates/ipam/vlan/template.j2`")
|
|
738
|
+
log_entries.get(message__contains="Addition - `graphql_queries/device_interfaces.gql`")
|
|
739
|
+
log_entries.get(message__contains="Addition - `graphql_queries/device_names.gql`")
|
|
714
740
|
log_entries.get(message__contains="Addition - `jobs/__init__.py`")
|
|
715
741
|
log_entries.get(message__contains="Addition - `jobs/my_job.py`")
|
|
716
742
|
except JobLogEntry.DoesNotExist:
|
|
@@ -721,6 +747,7 @@ class GitTest(TransactionTestCase):
|
|
|
721
747
|
self.assertFalse(ConfigContextSchema.objects.filter(owner_object_id=self.repo.pk).exists())
|
|
722
748
|
self.assertFalse(ConfigContext.objects.filter(owner_object_id=self.repo.pk).exists())
|
|
723
749
|
self.assertFalse(ExportTemplate.objects.filter(owner_object_id=self.repo.pk).exists())
|
|
750
|
+
self.assertFalse(GraphQLQuery.objects.filter(owner_object_id=self.repo.pk).exists())
|
|
724
751
|
self.assertFalse(Job.objects.filter(module_name__startswith=self.repo.slug).exists())
|
|
725
752
|
|
|
726
753
|
# TODO: test dry-run against a branch name
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
from datetime import datetime, timedelta, timezone
|
|
2
2
|
import os
|
|
3
|
+
import shutil
|
|
3
4
|
import tempfile
|
|
4
5
|
from unittest import expectedFailure, mock
|
|
5
6
|
import uuid
|
|
@@ -17,6 +18,7 @@ from django.test import override_settings
|
|
|
17
18
|
from django.test.utils import isolate_apps
|
|
18
19
|
from django.utils.timezone import get_default_timezone, now
|
|
19
20
|
from django_celery_beat.tzcrontab import TzAwareCrontab
|
|
21
|
+
from git import GitCommandError
|
|
20
22
|
from jinja2.exceptions import TemplateAssertionError, TemplateSyntaxError
|
|
21
23
|
import time_machine
|
|
22
24
|
|
|
@@ -48,6 +50,7 @@ from nautobot.extras.constants import (
|
|
|
48
50
|
JOB_LOG_MAX_LOG_OBJECT_LENGTH,
|
|
49
51
|
JOB_OVERRIDABLE_FIELDS,
|
|
50
52
|
)
|
|
53
|
+
from nautobot.extras.datasources.registry import get_datasource_contents
|
|
51
54
|
from nautobot.extras.jobs import get_job
|
|
52
55
|
from nautobot.extras.models import (
|
|
53
56
|
ComputedField,
|
|
@@ -83,6 +86,7 @@ from nautobot.extras.models import (
|
|
|
83
86
|
from nautobot.extras.models.statuses import StatusModel
|
|
84
87
|
from nautobot.extras.registry import registry
|
|
85
88
|
from nautobot.extras.secrets.exceptions import SecretParametersError, SecretProviderError, SecretValueNotFoundError
|
|
89
|
+
from nautobot.extras.tests.git_helper import create_and_populate_git_repository
|
|
86
90
|
from nautobot.ipam.models import IPAddress
|
|
87
91
|
from nautobot.tenancy.models import Tenant
|
|
88
92
|
from nautobot.virtualization.models import (
|
|
@@ -1072,6 +1076,285 @@ class GitRepositoryTest(ModelTestCases.BaseModelTestCase):
|
|
|
1072
1076
|
self.repo.remote_url = "http://some-private-host/example.git"
|
|
1073
1077
|
self.repo.validated_save()
|
|
1074
1078
|
|
|
1079
|
+
def test_clone_to_directory_context_manager(self):
|
|
1080
|
+
"""Confirm that the clone_to_directory_context() context manager method works as expected."""
|
|
1081
|
+
try:
|
|
1082
|
+
specified_path = tempfile.mkdtemp()
|
|
1083
|
+
self.tempdir = tempfile.TemporaryDirectory() # pylint: disable=consider-using-with
|
|
1084
|
+
create_and_populate_git_repository(self.tempdir.name, divergent_branch="divergent-branch")
|
|
1085
|
+
self.repo_slug = "new_git_repo"
|
|
1086
|
+
self.repo = GitRepository(
|
|
1087
|
+
name="New Git Repository",
|
|
1088
|
+
slug=self.repo_slug,
|
|
1089
|
+
remote_url="file://"
|
|
1090
|
+
+ self.tempdir.name, # file:// URLs aren't permitted normally, but very useful here!
|
|
1091
|
+
branch="main",
|
|
1092
|
+
# Provide everything we know we can provide
|
|
1093
|
+
provided_contents=[
|
|
1094
|
+
entry.content_identifier for entry in get_datasource_contents("extras.gitrepository")
|
|
1095
|
+
],
|
|
1096
|
+
)
|
|
1097
|
+
self.repo.save()
|
|
1098
|
+
with self.subTest("Clone a repository with no path argument provided"):
|
|
1099
|
+
with self.repo.clone_to_directory_context() as path:
|
|
1100
|
+
# assert that the temporary directory was created in the expected location i.e. /tmp/
|
|
1101
|
+
self.assertTrue(path.startswith(tempfile.gettempdir()))
|
|
1102
|
+
self.assertTrue(os.path.exists(path))
|
|
1103
|
+
self.assertTrue(os.path.exists(path + "/config_context_schemas/badschema1.json"))
|
|
1104
|
+
self.assertTrue(os.path.exists(path + "/config_context_schemas/badschema2.json"))
|
|
1105
|
+
self.assertFalse(os.path.exists(path))
|
|
1106
|
+
|
|
1107
|
+
with self.subTest("Clone a repository with a path argument provided"):
|
|
1108
|
+
with self.repo.clone_to_directory_context(path=specified_path) as path:
|
|
1109
|
+
# assert that the temporary directory was created in the expected location i.e. /tmp/
|
|
1110
|
+
self.assertTrue(path.startswith(specified_path))
|
|
1111
|
+
self.assertTrue(os.path.exists(path))
|
|
1112
|
+
self.assertTrue(os.path.exists(path + "/config_context_schemas/badschema1.json"))
|
|
1113
|
+
self.assertTrue(os.path.exists(path + "/config_context_schemas/badschema2.json"))
|
|
1114
|
+
# Temp directory is cleaned up after the context manager exits
|
|
1115
|
+
self.assertFalse(os.path.exists(path))
|
|
1116
|
+
|
|
1117
|
+
with self.subTest("Clone a repository with the branch argument provided"):
|
|
1118
|
+
with self.repo.clone_to_directory_context(path=specified_path, branch="main") as path:
|
|
1119
|
+
# assert that the temporary directory was created in the expected location i.e. /tmp/
|
|
1120
|
+
self.assertTrue(path.startswith(specified_path))
|
|
1121
|
+
self.assertTrue(os.path.exists(path))
|
|
1122
|
+
self.assertTrue(os.path.exists(path + "/config_context_schemas/badschema1.json"))
|
|
1123
|
+
self.assertTrue(os.path.exists(path + "/config_context_schemas/badschema2.json"))
|
|
1124
|
+
# Temp directory is cleaned up after the context manager exits
|
|
1125
|
+
self.assertFalse(os.path.exists(path))
|
|
1126
|
+
|
|
1127
|
+
with self.subTest("Clone a repository with non-default branch provided"):
|
|
1128
|
+
with self.repo.clone_to_directory_context(path=specified_path, branch="empty-repo") as path:
|
|
1129
|
+
# assert that the temporary directory was created in the expected location i.e. /tmp/
|
|
1130
|
+
self.assertTrue(path.startswith(specified_path))
|
|
1131
|
+
self.assertTrue(os.path.exists(path))
|
|
1132
|
+
# empty-repo should contain no files
|
|
1133
|
+
self.assertFalse(os.path.exists(path + "/config_context_schemas"))
|
|
1134
|
+
self.assertFalse(os.path.exists(path + "/config_contexts"))
|
|
1135
|
+
# Temp directory is cleaned up after the context manager exits
|
|
1136
|
+
self.assertFalse(os.path.exists(path))
|
|
1137
|
+
|
|
1138
|
+
with self.subTest("Clone a repository with divergent branch provided"):
|
|
1139
|
+
with self.repo.clone_to_directory_context(path=specified_path, branch="divergent-branch") as path:
|
|
1140
|
+
# assert that the temporary directory was created in the expected location i.e. /tmp/
|
|
1141
|
+
self.assertTrue(path.startswith(specified_path))
|
|
1142
|
+
self.assertTrue(os.path.exists(path))
|
|
1143
|
+
self.assertTrue(os.path.exists(path + "/config_context_schemas/badschema1.json"))
|
|
1144
|
+
self.assertTrue(os.path.exists(path + "/config_context_schemas/badschema2.json"))
|
|
1145
|
+
# Temp directory is cleaned up after the context manager exits
|
|
1146
|
+
self.assertFalse(os.path.exists(path))
|
|
1147
|
+
|
|
1148
|
+
with self.subTest("Clone a repository with the head argument provided"):
|
|
1149
|
+
with self.repo.clone_to_directory_context(path=specified_path, head="valid-files") as path:
|
|
1150
|
+
# assert that the temporary directory was created in the expected location i.e. /tmp/
|
|
1151
|
+
self.assertTrue(path.startswith(specified_path))
|
|
1152
|
+
self.assertTrue(os.path.exists(path))
|
|
1153
|
+
self.assertTrue(os.path.exists(path + "/config_context_schemas/schema-1.yaml"))
|
|
1154
|
+
self.assertTrue(os.path.exists(path + "/config_contexts/context.yaml"))
|
|
1155
|
+
# Temp directory is cleaned up after the context manager exits
|
|
1156
|
+
self.assertFalse(os.path.exists(path))
|
|
1157
|
+
|
|
1158
|
+
with self.subTest("Clone a repository with depth argument provided"):
|
|
1159
|
+
with self.repo.clone_to_directory_context(path=specified_path, depth=1) as path:
|
|
1160
|
+
# assert that the temporary directory was created in the expected location i.e. /tmp/
|
|
1161
|
+
self.assertTrue(path.startswith(specified_path))
|
|
1162
|
+
self.assertTrue(os.path.exists(path))
|
|
1163
|
+
self.assertTrue(os.path.exists(path + "/config_context_schemas/badschema1.json"))
|
|
1164
|
+
self.assertTrue(os.path.exists(path + "/config_contexts/badcontext2.json"))
|
|
1165
|
+
# Temp directory is cleaned up after the context manager exits
|
|
1166
|
+
self.assertFalse(os.path.exists(path))
|
|
1167
|
+
|
|
1168
|
+
with self.subTest("Clone a shallow repository with depth and valid head arguments provided"):
|
|
1169
|
+
with self.repo.clone_to_directory_context(
|
|
1170
|
+
path=specified_path, depth=1, head="divergent-branch-tag"
|
|
1171
|
+
) as path:
|
|
1172
|
+
# assert that the temporary directory was created in the expected location i.e. /tmp/
|
|
1173
|
+
self.assertTrue(path.startswith(specified_path))
|
|
1174
|
+
self.assertTrue(os.path.exists(path))
|
|
1175
|
+
self.assertTrue(os.path.exists(path + "/config_context_schemas/badschema1.json"))
|
|
1176
|
+
self.assertTrue(os.path.exists(path + "/config_contexts/badcontext2.json"))
|
|
1177
|
+
# Temp directory is cleaned up after the context manager exits
|
|
1178
|
+
self.assertFalse(os.path.exists(path))
|
|
1179
|
+
|
|
1180
|
+
with self.subTest("Clone a shallow repository with depth and invalid head arguments provided"):
|
|
1181
|
+
with self.assertRaisesRegex(GitCommandError, "malformed object name valid-files"):
|
|
1182
|
+
# Shallow copy a repo should only have the latest commit
|
|
1183
|
+
with self.repo.clone_to_directory_context(path=specified_path, depth=1, head="valid-files") as path:
|
|
1184
|
+
pass
|
|
1185
|
+
|
|
1186
|
+
with self.subTest("Clone a shallow repository with depth and valid branch arguments provided"):
|
|
1187
|
+
with self.repo.clone_to_directory_context(path=specified_path, depth=1, branch="main") as path:
|
|
1188
|
+
# assert that the temporary directory was created in the expected location i.e. /tmp/
|
|
1189
|
+
self.assertTrue(path.startswith(specified_path))
|
|
1190
|
+
self.assertTrue(os.path.exists(path))
|
|
1191
|
+
self.assertTrue(os.path.exists(path + "/config_context_schemas/badschema1.json"))
|
|
1192
|
+
self.assertTrue(os.path.exists(path + "/config_contexts/badcontext2.json"))
|
|
1193
|
+
# Temp directory is cleaned up after the context manager exits
|
|
1194
|
+
self.assertFalse(os.path.exists(path))
|
|
1195
|
+
|
|
1196
|
+
with self.subTest("Clone a shallow repository with depth and divergent branch arguments provided"):
|
|
1197
|
+
with self.repo.clone_to_directory_context(
|
|
1198
|
+
path=specified_path, depth=1, branch="divergent-branch"
|
|
1199
|
+
) as path:
|
|
1200
|
+
# assert that the temporary directory was created in the expected location i.e. /tmp/
|
|
1201
|
+
self.assertTrue(path.startswith(specified_path))
|
|
1202
|
+
self.assertTrue(os.path.exists(path))
|
|
1203
|
+
self.assertTrue(os.path.exists(path + "/config_context_schemas/badschema1.json"))
|
|
1204
|
+
self.assertTrue(os.path.exists(path + "/config_contexts/badcontext2.json"))
|
|
1205
|
+
# Temp directory is cleaned up after the context manager exits
|
|
1206
|
+
self.assertFalse(os.path.exists(path))
|
|
1207
|
+
|
|
1208
|
+
with self.subTest("Assert a GitCommandError is raised when an invalid commit hash is provided"):
|
|
1209
|
+
with self.assertRaisesRegex(GitCommandError, "malformed object name non-existent"):
|
|
1210
|
+
with self.repo.clone_to_directory_context(path=specified_path, head="non-existent") as path:
|
|
1211
|
+
pass
|
|
1212
|
+
|
|
1213
|
+
with self.subTest("Assert a value error is raised when branch and head are both provided"):
|
|
1214
|
+
with self.assertRaisesRegex(ValueError, "Cannot specify both branch and head"):
|
|
1215
|
+
with self.repo.clone_to_directory_context(branch="main", head="valid-files") as path:
|
|
1216
|
+
pass
|
|
1217
|
+
finally:
|
|
1218
|
+
shutil.rmtree(specified_path, ignore_errors=True)
|
|
1219
|
+
shutil.rmtree(self.tempdir.name, ignore_errors=True)
|
|
1220
|
+
|
|
1221
|
+
def test_clone_to_directory_helper_methods(self):
|
|
1222
|
+
"""Confirm that the clone_to_directory()/cleanup_cloned_directory() methods work as expected."""
|
|
1223
|
+
try:
|
|
1224
|
+
specified_path = tempfile.mkdtemp()
|
|
1225
|
+
self.tempdir = tempfile.TemporaryDirectory() # pylint: disable=consider-using-with
|
|
1226
|
+
create_and_populate_git_repository(self.tempdir.name, divergent_branch="divergent-branch")
|
|
1227
|
+
self.repo_slug = "new_git_repo"
|
|
1228
|
+
self.repo = GitRepository(
|
|
1229
|
+
name="New Git Repository",
|
|
1230
|
+
slug=self.repo_slug,
|
|
1231
|
+
remote_url="file://"
|
|
1232
|
+
+ self.tempdir.name, # file:// URLs aren't permitted normally, but very useful here!
|
|
1233
|
+
branch="main",
|
|
1234
|
+
# Provide everything we know we can provide
|
|
1235
|
+
provided_contents=[
|
|
1236
|
+
entry.content_identifier for entry in get_datasource_contents("extras.gitrepository")
|
|
1237
|
+
],
|
|
1238
|
+
)
|
|
1239
|
+
self.repo.save()
|
|
1240
|
+
with self.subTest("Clone a repository with no path argument provided"):
|
|
1241
|
+
path = self.repo.clone_to_directory()
|
|
1242
|
+
# assert that the temporary directory was created in the expected location i.e. /tmp/
|
|
1243
|
+
self.assertTrue(path.startswith(tempfile.gettempdir()))
|
|
1244
|
+
self.assertTrue(os.path.exists(path))
|
|
1245
|
+
self.assertTrue(os.path.exists(path + "/config_context_schemas/badschema1.json"))
|
|
1246
|
+
self.assertTrue(os.path.exists(path + "/config_context_schemas/badschema2.json"))
|
|
1247
|
+
self.repo.cleanup_cloned_directory(path)
|
|
1248
|
+
self.assertFalse(os.path.exists(path))
|
|
1249
|
+
|
|
1250
|
+
with self.subTest("Clone a repository with a path argument provided"):
|
|
1251
|
+
path = self.repo.clone_to_directory(path=specified_path)
|
|
1252
|
+
# assert that the temporary directory was created in the expected location i.e. /tmp/
|
|
1253
|
+
self.assertTrue(path.startswith(specified_path))
|
|
1254
|
+
self.assertTrue(os.path.exists(path))
|
|
1255
|
+
self.assertTrue(os.path.exists(path + "/config_context_schemas/badschema1.json"))
|
|
1256
|
+
self.assertTrue(os.path.exists(path + "/config_context_schemas/badschema2.json"))
|
|
1257
|
+
self.repo.cleanup_cloned_directory(path)
|
|
1258
|
+
self.assertFalse(os.path.exists(path))
|
|
1259
|
+
|
|
1260
|
+
with self.subTest("Clone a repository with the branch argument provided"):
|
|
1261
|
+
path = self.repo.clone_to_directory(path=specified_path, branch="main")
|
|
1262
|
+
# assert that the temporary directory was created in the expected location i.e. /tmp/
|
|
1263
|
+
self.assertTrue(path.startswith(specified_path))
|
|
1264
|
+
self.assertTrue(os.path.exists(path))
|
|
1265
|
+
self.assertTrue(os.path.exists(path + "/config_context_schemas/badschema1.json"))
|
|
1266
|
+
self.assertTrue(os.path.exists(path + "/config_context_schemas/badschema2.json"))
|
|
1267
|
+
self.repo.cleanup_cloned_directory(path)
|
|
1268
|
+
self.assertFalse(os.path.exists(path))
|
|
1269
|
+
|
|
1270
|
+
with self.subTest("Clone a repository with non-default branch provided"):
|
|
1271
|
+
path = self.repo.clone_to_directory(path=specified_path, branch="empty-repo")
|
|
1272
|
+
# assert that the temporary directory was created in the expected location i.e. /tmp/
|
|
1273
|
+
self.assertTrue(path.startswith(specified_path))
|
|
1274
|
+
self.assertTrue(os.path.exists(path))
|
|
1275
|
+
self.assertFalse(os.path.exists(path + "/config_context_schemas"))
|
|
1276
|
+
self.assertFalse(os.path.exists(path + "/config_contexts"))
|
|
1277
|
+
self.repo.cleanup_cloned_directory(path)
|
|
1278
|
+
self.assertFalse(os.path.exists(path))
|
|
1279
|
+
|
|
1280
|
+
with self.subTest("Clone a repository with divergent branch provided"):
|
|
1281
|
+
path = self.repo.clone_to_directory(path=specified_path, branch="divergent-branch")
|
|
1282
|
+
# assert that the temporary directory was created in the expected location i.e. /tmp/
|
|
1283
|
+
self.assertTrue(path.startswith(specified_path))
|
|
1284
|
+
self.assertTrue(os.path.exists(path))
|
|
1285
|
+
self.assertTrue(os.path.exists(path + "/config_context_schemas"))
|
|
1286
|
+
self.assertTrue(os.path.exists(path + "/config_contexts"))
|
|
1287
|
+
self.repo.cleanup_cloned_directory(path)
|
|
1288
|
+
self.assertFalse(os.path.exists(path))
|
|
1289
|
+
|
|
1290
|
+
with self.subTest("Clone a repository with the head argument provided"):
|
|
1291
|
+
path = self.repo.clone_to_directory(path=specified_path, head="valid-files")
|
|
1292
|
+
# assert that the temporary directory was created in the expected location i.e. /tmp/
|
|
1293
|
+
self.assertTrue(path.startswith(specified_path))
|
|
1294
|
+
self.assertTrue(os.path.exists(path))
|
|
1295
|
+
self.assertTrue(os.path.exists(path + "/config_context_schemas/schema-1.yaml"))
|
|
1296
|
+
self.assertTrue(os.path.exists(path + "/config_contexts/context.yaml"))
|
|
1297
|
+
self.repo.cleanup_cloned_directory(path)
|
|
1298
|
+
self.assertFalse(os.path.exists(path))
|
|
1299
|
+
|
|
1300
|
+
with self.subTest("Clone a repository with depth argument provided"):
|
|
1301
|
+
path = self.repo.clone_to_directory(path=specified_path, depth=1)
|
|
1302
|
+
# assert that the temporary directory was created in the expected location i.e. /tmp/
|
|
1303
|
+
self.assertTrue(path.startswith(specified_path))
|
|
1304
|
+
self.assertTrue(os.path.exists(path))
|
|
1305
|
+
self.assertTrue(os.path.exists(path + "/config_context_schemas/badschema1.json"))
|
|
1306
|
+
self.assertTrue(os.path.exists(path + "/config_contexts/badcontext2.json"))
|
|
1307
|
+
self.repo.cleanup_cloned_directory(path)
|
|
1308
|
+
self.assertFalse(os.path.exists(path))
|
|
1309
|
+
|
|
1310
|
+
with self.subTest("Clone a shallow repository with depth and valid head arguments provided"):
|
|
1311
|
+
path = self.repo.clone_to_directory(path=specified_path, depth=1, head="divergent-branch-tag")
|
|
1312
|
+
# assert that the temporary directory was created in the expected location i.e. /tmp/
|
|
1313
|
+
self.assertTrue(path.startswith(specified_path))
|
|
1314
|
+
self.assertTrue(os.path.exists(path))
|
|
1315
|
+
self.assertTrue(os.path.exists(path + "/config_context_schemas/badschema1.json"))
|
|
1316
|
+
self.assertTrue(os.path.exists(path + "/config_contexts/badcontext2.json"))
|
|
1317
|
+
self.repo.cleanup_cloned_directory(path)
|
|
1318
|
+
self.assertFalse(os.path.exists(path))
|
|
1319
|
+
|
|
1320
|
+
with self.subTest("Clone a shallow repository with depth and invalid head arguments provided"):
|
|
1321
|
+
with self.assertRaisesRegex(GitCommandError, "malformed object name valid-files"):
|
|
1322
|
+
# Shallow copy a repo should only have the latest commit
|
|
1323
|
+
path = self.repo.clone_to_directory(path=specified_path, depth=1, head="valid-files")
|
|
1324
|
+
self.repo.cleanup_cloned_directory(path)
|
|
1325
|
+
self.assertFalse(os.path.exists(path))
|
|
1326
|
+
|
|
1327
|
+
with self.subTest("Clone a shallow repository with depth and valid branch arguments provided"):
|
|
1328
|
+
path = self.repo.clone_to_directory(path=specified_path, depth=1, branch="main")
|
|
1329
|
+
# assert that the temporary directory was created in the expected location i.e. /tmp/
|
|
1330
|
+
self.assertTrue(path.startswith(specified_path))
|
|
1331
|
+
self.assertTrue(os.path.exists(path))
|
|
1332
|
+
self.assertTrue(os.path.exists(path + "/config_context_schemas/badschema1.json"))
|
|
1333
|
+
self.assertTrue(os.path.exists(path + "/config_contexts/badcontext2.json"))
|
|
1334
|
+
self.repo.cleanup_cloned_directory(path)
|
|
1335
|
+
self.assertFalse(os.path.exists(path))
|
|
1336
|
+
|
|
1337
|
+
with self.subTest("Clone a shallow repository with depth and divergent branch arguments provided"):
|
|
1338
|
+
path = self.repo.clone_to_directory(path=specified_path, depth=1, branch="divergent-branch")
|
|
1339
|
+
# assert that the temporary directory was created in the expected location i.e. /tmp/
|
|
1340
|
+
self.assertTrue(path.startswith(specified_path))
|
|
1341
|
+
self.assertTrue(os.path.exists(path))
|
|
1342
|
+
self.assertTrue(os.path.exists(path + "/config_context_schemas/badschema1.json"))
|
|
1343
|
+
self.assertTrue(os.path.exists(path + "/config_contexts/badcontext2.json"))
|
|
1344
|
+
self.repo.cleanup_cloned_directory(path)
|
|
1345
|
+
self.assertFalse(os.path.exists(path))
|
|
1346
|
+
|
|
1347
|
+
with self.subTest("Assert a GitCommandError is raised when an invalid commit hash is provided"):
|
|
1348
|
+
with self.assertRaisesRegex(GitCommandError, "malformed object name non-existent"):
|
|
1349
|
+
path = self.repo.clone_to_directory(path=specified_path, head="non-existent")
|
|
1350
|
+
|
|
1351
|
+
with self.subTest("Assert a ValuError is raised when branch and head are both provided"):
|
|
1352
|
+
with self.assertRaisesRegex(ValueError, "Cannot specify both branch and head"):
|
|
1353
|
+
path = self.repo.clone_to_directory(branch="main", head="valid-files")
|
|
1354
|
+
finally:
|
|
1355
|
+
shutil.rmtree(specified_path, ignore_errors=True)
|
|
1356
|
+
shutil.rmtree(self.tempdir.name, ignore_errors=True)
|
|
1357
|
+
|
|
1075
1358
|
|
|
1076
1359
|
class JobModelTest(ModelTestCases.BaseModelTestCase):
|
|
1077
1360
|
"""
|