nautobot 2.4.3__py3-none-any.whl → 2.4.5__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/__init__.py +19 -3
- nautobot/apps/filters.py +2 -0
- nautobot/circuits/filters.py +1 -1
- nautobot/circuits/tests/test_models.py +5 -3
- nautobot/cloud/filters.py +3 -6
- nautobot/cloud/tests/test_filters.py +21 -0
- nautobot/core/admin.py +2 -0
- nautobot/core/celery/__init__.py +5 -3
- nautobot/core/jobs/__init__.py +5 -3
- nautobot/core/management/commands/generate_performance_test_endpoints.py +9 -6
- nautobot/core/models/utils.py +6 -1
- nautobot/core/templates/inc/javascript.html +1 -0
- nautobot/core/templatetags/ui_framework.py +20 -4
- nautobot/core/testing/__init__.py +2 -0
- nautobot/core/testing/forms.py +1 -1
- nautobot/core/testing/mixins.py +9 -0
- nautobot/core/tests/test_api.py +1 -1
- nautobot/core/tests/test_graphql.py +3 -3
- nautobot/core/tests/test_jobs.py +30 -28
- nautobot/core/ui/object_detail.py +1 -1
- nautobot/dcim/api/serializers.py +36 -0
- nautobot/dcim/api/views.py +1 -1
- nautobot/dcim/elevations.py +17 -4
- nautobot/dcim/factory.py +9 -1
- nautobot/dcim/filters/__init__.py +27 -1
- nautobot/dcim/forms.py +13 -1
- nautobot/dcim/models/devices.py +11 -5
- nautobot/dcim/signals.py +26 -0
- nautobot/dcim/templates/dcim/virtualdevicecontext_retrieve.html +0 -62
- nautobot/dcim/templates/dcim/virtualdevicecontext_update.html +6 -0
- nautobot/dcim/tests/test_api.py +176 -0
- nautobot/dcim/tests/test_filters.py +56 -3
- nautobot/dcim/tests/test_jobs.py +4 -6
- nautobot/dcim/tests/test_models.py +40 -0
- nautobot/dcim/views.py +24 -14
- nautobot/extras/api/mixins.py +1 -1
- nautobot/extras/api/views.py +2 -2
- nautobot/extras/choices.py +8 -3
- nautobot/extras/filters/__init__.py +4 -0
- nautobot/extras/jobs.py +181 -103
- nautobot/extras/management/utils.py +13 -2
- nautobot/extras/models/datasources.py +11 -4
- nautobot/extras/models/jobs.py +20 -17
- nautobot/extras/plugins/__init__.py +26 -1
- nautobot/extras/tables.py +25 -29
- nautobot/extras/templates/extras/inc/jobresult.html +12 -13
- nautobot/extras/templates/extras/objectchange.html +28 -12
- nautobot/extras/test_jobs/atomic_transaction.py +6 -6
- nautobot/extras/test_jobs/fail.py +75 -1
- nautobot/extras/tests/test_api.py +17 -16
- nautobot/extras/tests/test_datasources.py +64 -54
- nautobot/extras/tests/test_filters.py +2 -0
- nautobot/extras/tests/test_jobs.py +69 -62
- nautobot/extras/tests/test_models.py +1 -1
- nautobot/extras/tests/test_plugins.py +32 -1
- nautobot/extras/tests/test_relationships.py +5 -5
- nautobot/extras/tests/test_views.py +12 -2
- nautobot/extras/views.py +10 -1
- nautobot/ipam/api/serializers.py +7 -8
- nautobot/ipam/api/views.py +2 -2
- nautobot/ipam/factory.py +27 -8
- nautobot/ipam/filters.py +67 -29
- nautobot/ipam/formfields.py +51 -0
- nautobot/ipam/forms.py +28 -1
- nautobot/ipam/migrations/0051_added_optional_vrf_relationship_to_vdc.py +41 -0
- nautobot/ipam/models.py +63 -5
- nautobot/ipam/querysets.py +6 -0
- nautobot/ipam/tables.py +21 -7
- nautobot/ipam/templates/ipam/rir.html +1 -43
- nautobot/ipam/tests/test_api.py +107 -66
- nautobot/ipam/tests/test_filters.py +145 -5
- nautobot/ipam/tests/test_models.py +16 -0
- nautobot/ipam/tests/test_views.py +15 -2
- nautobot/ipam/urls.py +1 -21
- nautobot/ipam/views.py +24 -41
- nautobot/project-static/css/base.css +11 -0
- nautobot/project-static/css/dark.css +2 -1
- nautobot/project-static/docs/code-reference/nautobot/apps/filters.html +62 -0
- nautobot/project-static/docs/code-reference/nautobot/apps/jobs.html +43 -5
- nautobot/project-static/docs/code-reference/nautobot/apps/testing.html +35 -0
- nautobot/project-static/docs/development/apps/api/configuration-view.html +0 -3
- nautobot/project-static/docs/development/apps/api/models/graphql.html +0 -4
- nautobot/project-static/docs/development/apps/api/platform-features/custom-validators.html +94 -1
- nautobot/project-static/docs/development/apps/api/platform-features/filter-extensions.html +0 -3
- nautobot/project-static/docs/development/apps/api/platform-features/jinja2-filters.html +0 -3
- nautobot/project-static/docs/development/apps/api/platform-features/populating-extensibility-features.html +0 -3
- nautobot/project-static/docs/development/apps/api/platform-features/secrets-providers.html +0 -3
- nautobot/project-static/docs/development/apps/api/prometheus.html +0 -3
- nautobot/project-static/docs/development/apps/api/testing.html +0 -6
- nautobot/project-static/docs/development/apps/api/ui-extensions/banners.html +0 -3
- nautobot/project-static/docs/development/apps/api/ui-extensions/home-page.html +0 -3
- nautobot/project-static/docs/development/apps/api/ui-extensions/object-views.html +0 -3
- nautobot/project-static/docs/development/apps/api/views/core-view-overrides.html +0 -3
- nautobot/project-static/docs/development/apps/api/views/nautobot-generic-views.html +1 -7
- nautobot/project-static/docs/development/apps/api/views/nautobotuiviewset.html +0 -7
- nautobot/project-static/docs/development/apps/api/views/nautobotuiviewsetrouter.html +0 -4
- nautobot/project-static/docs/development/apps/api/views/notes.html +0 -3
- nautobot/project-static/docs/development/apps/index.html +2 -35
- nautobot/project-static/docs/development/apps/migration/code-updates.html +1 -1
- nautobot/project-static/docs/development/core/application-registry.html +0 -6
- nautobot/project-static/docs/development/core/best-practices.html +0 -27
- nautobot/project-static/docs/development/core/docker-compose-advanced-use-cases.html +58 -4
- nautobot/project-static/docs/development/core/getting-started.html +12 -16
- nautobot/project-static/docs/development/core/homepage.html +0 -3
- nautobot/project-static/docs/development/core/style-guide.html +0 -5
- nautobot/project-static/docs/development/core/templates.html +0 -3
- nautobot/project-static/docs/development/core/testing.html +0 -9
- nautobot/project-static/docs/development/jobs/index.html +30 -43
- nautobot/project-static/docs/objects.inv +0 -0
- nautobot/project-static/docs/overview/application_stack.html +0 -18
- nautobot/project-static/docs/release-notes/version-2.4.html +374 -0
- nautobot/project-static/docs/requirements.txt +2 -2
- nautobot/project-static/docs/search/search_index.json +1 -1
- nautobot/project-static/docs/sitemap.xml +290 -290
- nautobot/project-static/docs/sitemap.xml.gz +0 -0
- nautobot/project-static/docs/user-guide/administration/configuration/settings.html +0 -10
- nautobot/project-static/docs/user-guide/administration/guides/docker.html +0 -15
- nautobot/project-static/docs/user-guide/administration/installation/index.html +0 -16
- nautobot/project-static/docs/user-guide/administration/installation/nautobot.html +1 -4
- nautobot/project-static/docs/user-guide/administration/installation/services.html +0 -11
- nautobot/project-static/docs/user-guide/administration/migration/migrating-from-postgresql.html +3 -3
- nautobot/project-static/docs/user-guide/administration/tools/nautobot-server.html +5 -35
- 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 +1 -1
- nautobot/project-static/docs/user-guide/administration/upgrading/upgrading.html +0 -4
- nautobot/project-static/docs/user-guide/core-data-model/circuits/providernetwork.html +0 -3
- nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleport.html +0 -4
- nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleporttemplate.html +0 -4
- nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleserverport.html +0 -4
- nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleserverporttemplate.html +0 -4
- nautobot/project-static/docs/user-guide/core-data-model/dcim/devicebay.html +0 -4
- nautobot/project-static/docs/user-guide/core-data-model/dcim/devicebaytemplate.html +0 -4
- nautobot/project-static/docs/user-guide/core-data-model/dcim/deviceredundancygroup.html +0 -3
- nautobot/project-static/docs/user-guide/core-data-model/dcim/devicetype.html +0 -4
- nautobot/project-static/docs/user-guide/core-data-model/dcim/frontport.html +0 -4
- nautobot/project-static/docs/user-guide/core-data-model/dcim/frontporttemplate.html +0 -4
- nautobot/project-static/docs/user-guide/core-data-model/dcim/interface.html +1 -17
- nautobot/project-static/docs/user-guide/core-data-model/dcim/interfaceredundancygroup.html +0 -3
- nautobot/project-static/docs/user-guide/core-data-model/dcim/interfacetemplate.html +0 -4
- nautobot/project-static/docs/user-guide/core-data-model/dcim/inventoryitem.html +0 -4
- nautobot/project-static/docs/user-guide/core-data-model/dcim/location.html +0 -3
- nautobot/project-static/docs/user-guide/core-data-model/dcim/locationtype.html +1 -7
- nautobot/project-static/docs/user-guide/core-data-model/dcim/platform.html +0 -4
- nautobot/project-static/docs/user-guide/core-data-model/dcim/poweroutlet.html +0 -4
- nautobot/project-static/docs/user-guide/core-data-model/dcim/poweroutlettemplate.html +0 -4
- nautobot/project-static/docs/user-guide/core-data-model/dcim/powerport.html +0 -4
- nautobot/project-static/docs/user-guide/core-data-model/dcim/powerporttemplate.html +0 -4
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rearport.html +0 -4
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rearporttemplate.html +0 -4
- nautobot/project-static/docs/user-guide/core-data-model/extras/configcontext.html +0 -6
- nautobot/project-static/docs/user-guide/core-data-model/extras/configcontextschema.html +0 -3
- nautobot/project-static/docs/user-guide/core-data-model/ipam/ipaddress.html +0 -4
- nautobot/project-static/docs/user-guide/core-data-model/ipam/vlan.html +0 -4
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/vminterface.html +0 -8
- nautobot/project-static/docs/user-guide/feature-guides/custom-fields.html +3 -3
- nautobot/project-static/docs/user-guide/feature-guides/graphql.html +0 -6
- nautobot/project-static/docs/user-guide/platform-functionality/computedfield.html +0 -3
- nautobot/project-static/docs/user-guide/platform-functionality/customfield.html +3 -15
- nautobot/project-static/docs/user-guide/platform-functionality/dynamicgroup.html +0 -26
- nautobot/project-static/docs/user-guide/platform-functionality/gitrepository.html +0 -8
- nautobot/project-static/docs/user-guide/platform-functionality/graphql.html +0 -3
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/index.html +0 -8
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/job-scheduling-and-approvals.html +0 -7
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobbutton.html +0 -3
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobhook.html +0 -3
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/models.html +0 -14
- nautobot/project-static/docs/user-guide/platform-functionality/note.html +0 -3
- nautobot/project-static/docs/user-guide/platform-functionality/relationship.html +1 -10
- nautobot/project-static/docs/user-guide/platform-functionality/rest-api/authentication.html +0 -3
- nautobot/project-static/docs/user-guide/platform-functionality/rest-api/filtering.html +0 -14
- nautobot/project-static/docs/user-guide/platform-functionality/rest-api/overview.html +0 -19
- nautobot/project-static/docs/user-guide/platform-functionality/secret.html +3 -9
- nautobot/project-static/docs/user-guide/platform-functionality/status.html +0 -8
- nautobot/project-static/docs/user-guide/platform-functionality/tag.html +0 -4
- nautobot/project-static/docs/user-guide/platform-functionality/template-filters.html +1 -13
- nautobot/project-static/docs/user-guide/platform-functionality/webhook.html +0 -5
- nautobot/project-static/js/editor.js +292 -0
- nautobot/project-static/monaco-editor-0.52.2/README.md +81 -0
- nautobot/project-static/monaco-editor-0.52.2/vs/base/browser/ui/codicons/codicon/codicon.ttf +0 -0
- nautobot/project-static/monaco-editor-0.52.2/vs/base/worker/workerMain.js +31 -0
- nautobot/project-static/monaco-editor-0.52.2/vs/basic-languages/xml/xml.js +10 -0
- nautobot/project-static/monaco-editor-0.52.2/vs/basic-languages/yaml/yaml.js +10 -0
- nautobot/project-static/monaco-editor-0.52.2/vs/editor/editor.main.css +8 -0
- nautobot/project-static/monaco-editor-0.52.2/vs/editor/editor.main.js +798 -0
- nautobot/project-static/monaco-editor-0.52.2/vs/language/json/jsonMode.js +19 -0
- nautobot/project-static/monaco-editor-0.52.2/vs/language/json/jsonWorker.js +42 -0
- nautobot/project-static/monaco-editor-0.52.2/vs/loader.js +11 -0
- nautobot/tenancy/filters/__init__.py +3 -5
- nautobot/tenancy/tests/test_filters.py +10 -0
- nautobot/virtualization/views.py +0 -1
- nautobot/wireless/tables.py +9 -4
- nautobot/wireless/tests/test_api.py +0 -9
- {nautobot-2.4.3.dist-info → nautobot-2.4.5.dist-info}/METADATA +4 -4
- {nautobot-2.4.3.dist-info → nautobot-2.4.5.dist-info}/RECORD +198 -186
- {nautobot-2.4.3.dist-info → nautobot-2.4.5.dist-info}/LICENSE.txt +0 -0
- {nautobot-2.4.3.dist-info → nautobot-2.4.5.dist-info}/NOTICE +0 -0
- {nautobot-2.4.3.dist-info → nautobot-2.4.5.dist-info}/WHEEL +0 -0
- {nautobot-2.4.3.dist-info → nautobot-2.4.5.dist-info}/entry_points.txt +0 -0
|
@@ -12,45 +12,43 @@
|
|
|
12
12
|
<strong>Summary of Results</strong>
|
|
13
13
|
</div>
|
|
14
14
|
<table class="table table-hover panel-body">
|
|
15
|
-
{% if result.job_model is not None %}
|
|
16
15
|
<tr>
|
|
17
16
|
<td>Job Description</td>
|
|
18
|
-
<td>{{ result.job_model.description | render_markdown }}</td>
|
|
17
|
+
<td>{{ result.job_model.description | render_markdown | placeholder }}</td>
|
|
19
18
|
</tr>
|
|
20
|
-
{% endif %}
|
|
21
19
|
<tr>
|
|
22
20
|
<td>Status</td>
|
|
23
21
|
<td><span id="pending-result-label">{% include 'extras/inc/job_label.html' with result=result %}</span></td>
|
|
24
22
|
</tr>
|
|
25
23
|
<tr>
|
|
26
24
|
<td>Started at</td>
|
|
27
|
-
<td>{{ result.date_created }}</td>
|
|
25
|
+
<td>{{ result.date_created | placeholder }}</td>
|
|
28
26
|
</tr>
|
|
29
27
|
<tr>
|
|
30
28
|
<td>User</td>
|
|
31
|
-
<td>{{ result.user }}</td>
|
|
29
|
+
<td>{{ result.user | placeholder }}</td>
|
|
32
30
|
</tr>
|
|
33
31
|
<tr>
|
|
34
32
|
<td>Duration</td>
|
|
35
33
|
<td>
|
|
36
|
-
{% if result.date_done %}
|
|
37
|
-
{{ result.duration }}
|
|
38
|
-
{% else %}
|
|
34
|
+
{% if result.date_created and not result.date_done %}
|
|
39
35
|
<img src="{% static 'img/ajax-loader.gif' %}">
|
|
36
|
+
{% else %}
|
|
37
|
+
{{ result.duration | placeholder}}
|
|
40
38
|
{% endif %}
|
|
41
39
|
</td>
|
|
42
40
|
</tr>
|
|
43
41
|
<tr>
|
|
44
42
|
<td>Return Value</td>
|
|
45
43
|
<td>
|
|
46
|
-
{% if result.date_done %}
|
|
44
|
+
{% if result.date_created and not result.date_done %}
|
|
45
|
+
<img src="{% static 'img/ajax-loader.gif' %}">
|
|
46
|
+
{% else %}
|
|
47
47
|
{% if result.result %}
|
|
48
48
|
<pre>{{ result.result | render_json }}</pre>
|
|
49
49
|
{% else %}
|
|
50
50
|
{{ result.result | placeholder }}
|
|
51
51
|
{% endif %}
|
|
52
|
-
{% else %}
|
|
53
|
-
<img src="{% static 'img/ajax-loader.gif' %}">
|
|
54
52
|
{% endif %}
|
|
55
53
|
</td>
|
|
56
54
|
</tr>
|
|
@@ -89,6 +87,7 @@
|
|
|
89
87
|
<input class="form-control" id="log-filter" type="text" placeholder="Filter log level or message" title="Filter log level or message" style="height: 23px" />
|
|
90
88
|
</div>
|
|
91
89
|
</div>
|
|
92
|
-
{%
|
|
90
|
+
{% if result and result.pk %}
|
|
91
|
+
{% ajax_table "log_table" "extras:jobresult_log-table" pk=result.pk %}
|
|
92
|
+
{% endif %}
|
|
93
93
|
</div>
|
|
94
|
-
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
{% extends 'generic/object_retrieve.html' %}
|
|
2
2
|
{% load helpers %}
|
|
3
|
+
{% load static %}
|
|
3
4
|
|
|
4
5
|
{% block title %}{{ object }}{% endblock %}
|
|
5
6
|
|
|
@@ -95,6 +96,20 @@
|
|
|
95
96
|
</tr>
|
|
96
97
|
</table>
|
|
97
98
|
</div>
|
|
99
|
+
<div class="panel panel-default">
|
|
100
|
+
<div class="panel-heading">
|
|
101
|
+
<strong>Object Data</strong>
|
|
102
|
+
</div>
|
|
103
|
+
<div class="panel-body">
|
|
104
|
+
<div class="editor-container"
|
|
105
|
+
data-lang="json"
|
|
106
|
+
data-value="{{ object.object_data|render_json:False }}"
|
|
107
|
+
style="max-height: 300px">
|
|
108
|
+
</div>
|
|
109
|
+
</div>
|
|
110
|
+
</div>
|
|
111
|
+
</div>
|
|
112
|
+
<div class="col-md-7">
|
|
98
113
|
<div class="panel panel-default">
|
|
99
114
|
<div class="panel-heading">
|
|
100
115
|
<strong>Difference</strong>
|
|
@@ -119,22 +134,17 @@
|
|
|
119
134
|
{% endif %}
|
|
120
135
|
</span>
|
|
121
136
|
{% else %}
|
|
122
|
-
<
|
|
123
|
-
|
|
137
|
+
<div class="editor-container"
|
|
138
|
+
data-mode="diff"
|
|
139
|
+
data-original="{{ diff_removed | render_json:False }}"
|
|
140
|
+
data-modified="{{ diff_added | render_json:False }}"
|
|
141
|
+
data-lang="json"
|
|
142
|
+
style="max-height: 730px">
|
|
143
|
+
</div>
|
|
124
144
|
{% endif %}
|
|
125
145
|
</div>
|
|
126
146
|
</div>
|
|
127
147
|
</div>
|
|
128
|
-
<div class="col-md-7">
|
|
129
|
-
<div class="panel panel-default">
|
|
130
|
-
<div class="panel-heading">
|
|
131
|
-
<strong>Object Data</strong>
|
|
132
|
-
</div>
|
|
133
|
-
<div class="panel-body">
|
|
134
|
-
<pre>{{ object.object_data|render_json }}</pre>
|
|
135
|
-
</div>
|
|
136
|
-
</div>
|
|
137
|
-
</div>
|
|
138
148
|
</div>
|
|
139
149
|
<div class="row">
|
|
140
150
|
<div class="col-md-12">
|
|
@@ -147,3 +157,9 @@
|
|
|
147
157
|
</div>
|
|
148
158
|
</div>
|
|
149
159
|
{% endblock %}
|
|
160
|
+
|
|
161
|
+
{% block javascript %}
|
|
162
|
+
{{ block.super }}
|
|
163
|
+
<script src="{% static 'js/editor.js' %}"></script>
|
|
164
|
+
{% endblock %}
|
|
165
|
+
|
|
@@ -16,13 +16,13 @@ class TestAtomicDecorator(Job):
|
|
|
16
16
|
Job that uses @transaction.atomic decorator to roll back changes.
|
|
17
17
|
"""
|
|
18
18
|
|
|
19
|
-
|
|
19
|
+
should_fail = BooleanVar()
|
|
20
20
|
|
|
21
21
|
@transaction.atomic
|
|
22
|
-
def run(self,
|
|
22
|
+
def run(self, should_fail=False): # pylint:disable=arguments-differ
|
|
23
23
|
try:
|
|
24
24
|
Status.objects.create(name="Test database atomic rollback 1")
|
|
25
|
-
if
|
|
25
|
+
if should_fail:
|
|
26
26
|
raise SimulatedError("simulated failure")
|
|
27
27
|
except Exception:
|
|
28
28
|
logger.error("Job failed, all database changes have been rolled back.")
|
|
@@ -35,13 +35,13 @@ class TestAtomicContextManager(Job):
|
|
|
35
35
|
Job that uses `with transaction.atomic()` context manager to roll back changes.
|
|
36
36
|
"""
|
|
37
37
|
|
|
38
|
-
|
|
38
|
+
should_fail = BooleanVar()
|
|
39
39
|
|
|
40
|
-
def run(self,
|
|
40
|
+
def run(self, should_fail=False): # pylint:disable=arguments-differ
|
|
41
41
|
try:
|
|
42
42
|
with transaction.atomic():
|
|
43
43
|
Status.objects.create(name="Test database atomic rollback 2")
|
|
44
|
-
if
|
|
44
|
+
if should_fail:
|
|
45
45
|
raise SimulatedError("simulated failure")
|
|
46
46
|
except Exception as err:
|
|
47
47
|
logger.error("Job failed, all database changes have been rolled back.")
|
|
@@ -62,6 +62,20 @@ class TestFailJob(Job):
|
|
|
62
62
|
logger.info("after_return() was called as expected")
|
|
63
63
|
|
|
64
64
|
|
|
65
|
+
class TestFailInBeforeStart(TestFailJob):
|
|
66
|
+
"""
|
|
67
|
+
Job that raises an exception in before_start().
|
|
68
|
+
"""
|
|
69
|
+
|
|
70
|
+
def before_start(self, task_id, args, kwargs):
|
|
71
|
+
super().before_start(task_id, args, kwargs)
|
|
72
|
+
logger.info("I'm a test job that fails!")
|
|
73
|
+
raise RunJobTaskFailed("Setup failure")
|
|
74
|
+
|
|
75
|
+
def run(self):
|
|
76
|
+
raise RuntimeError("run() was unexpectedly called after a failure in before_start()")
|
|
77
|
+
|
|
78
|
+
|
|
65
79
|
class TestFailWithSanitization(Job):
|
|
66
80
|
"""
|
|
67
81
|
Job with fail result that should be sanitized.
|
|
@@ -91,4 +105,64 @@ class TestFailWithSanitization(Job):
|
|
|
91
105
|
raise exc
|
|
92
106
|
|
|
93
107
|
|
|
94
|
-
|
|
108
|
+
class TestFailCleanly(TestFailJob):
|
|
109
|
+
"""
|
|
110
|
+
Job that fails "cleanly" through self.fail() instead of raising an exception.
|
|
111
|
+
"""
|
|
112
|
+
|
|
113
|
+
def run(self): # pylint: disable=arguments-differ
|
|
114
|
+
logger.info("I'm a test job that fails!")
|
|
115
|
+
self.fail("Failure")
|
|
116
|
+
return "We failed"
|
|
117
|
+
|
|
118
|
+
def on_failure(self, exc, task_id, args, kwargs, einfo):
|
|
119
|
+
if exc != "We failed":
|
|
120
|
+
raise RuntimeError(f"Expected exc to be the message returned from run(), but it was {exc!r}")
|
|
121
|
+
if task_id != self.request.id: # pylint: disable=no-member
|
|
122
|
+
raise RuntimeError(f"Expected task_id {task_id} to equal self.request.id {self.request.id}") # pylint: disable=no-member
|
|
123
|
+
if args:
|
|
124
|
+
raise RuntimeError(f"Expected args to be empty, but it was {args!r}")
|
|
125
|
+
if kwargs:
|
|
126
|
+
raise RuntimeError(f"Expected kwargs to be empty, but it was {kwargs!r}")
|
|
127
|
+
if einfo is not None:
|
|
128
|
+
raise RuntimeError(f"Expected einfo to be None, but it was {einfo!r}")
|
|
129
|
+
logger.info("on_failure() was called as expected")
|
|
130
|
+
|
|
131
|
+
def after_return(self, status, retval, task_id, args, kwargs, einfo):
|
|
132
|
+
if status is not JobResultStatusChoices.STATUS_FAILURE:
|
|
133
|
+
raise RuntimeError(f"Expected status to be {JobResultStatusChoices.STATUS_FAILURE}, but it was {status!r}")
|
|
134
|
+
if retval != "We failed":
|
|
135
|
+
raise RuntimeError(f"Expected retval to be the message returned from run(), but it was {retval!r}")
|
|
136
|
+
if task_id != self.request.id: # pylint: disable=no-member
|
|
137
|
+
raise RuntimeError(f"Expected task_id {task_id} to equal self.request.id {self.request.id}") # pylint: disable=no-member
|
|
138
|
+
if args:
|
|
139
|
+
raise RuntimeError(f"Expected args to be empty, but it was {args!r}")
|
|
140
|
+
if kwargs:
|
|
141
|
+
raise RuntimeError(f"Expected kwargs to be empty, but it was {kwargs!r}")
|
|
142
|
+
if einfo is not None:
|
|
143
|
+
raise RuntimeError(f"Expected einfo to be None, but it was {einfo!r}")
|
|
144
|
+
logger.info("after_return() was called as expected")
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
class TestFailCleanlyInBeforeStart(TestFailCleanly):
|
|
148
|
+
"""
|
|
149
|
+
Job that fails "cleanly" during before_start() through self.fail() instead of raising an exception.
|
|
150
|
+
"""
|
|
151
|
+
|
|
152
|
+
def before_start(self, task_id, args, kwargs):
|
|
153
|
+
super().before_start(task_id, args, kwargs)
|
|
154
|
+
logger.info("I'm a test job that fails!")
|
|
155
|
+
self.fail("We failed")
|
|
156
|
+
return "We failed"
|
|
157
|
+
|
|
158
|
+
def run(self):
|
|
159
|
+
raise RuntimeError("run() was unexpectedly called after a failure in before_start()")
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
register_jobs(
|
|
163
|
+
TestFailJob,
|
|
164
|
+
TestFailInBeforeStart,
|
|
165
|
+
TestFailWithSanitization,
|
|
166
|
+
TestFailCleanly,
|
|
167
|
+
TestFailCleanlyInBeforeStart,
|
|
168
|
+
)
|
|
@@ -196,20 +196,6 @@ class ComputedFieldTest(APIViewTestCases.APIViewTestCase):
|
|
|
196
196
|
|
|
197
197
|
class ConfigContextTest(APIViewTestCases.APIViewTestCase):
|
|
198
198
|
model = ConfigContext
|
|
199
|
-
create_data = [
|
|
200
|
-
{
|
|
201
|
-
"name": "Config Context 4",
|
|
202
|
-
"data": {"more_foo": True},
|
|
203
|
-
},
|
|
204
|
-
{
|
|
205
|
-
"name": "Config Context 5",
|
|
206
|
-
"data": {"more_bar": False},
|
|
207
|
-
},
|
|
208
|
-
{
|
|
209
|
-
"name": "Config Context 6",
|
|
210
|
-
"data": {"more_baz": None},
|
|
211
|
-
},
|
|
212
|
-
]
|
|
213
199
|
bulk_update_data = {
|
|
214
200
|
"description": "New description",
|
|
215
201
|
}
|
|
@@ -220,6 +206,21 @@ class ConfigContextTest(APIViewTestCases.APIViewTestCase):
|
|
|
220
206
|
ConfigContext.objects.create(name="Config Context 1", weight=100, data={"foo": 123})
|
|
221
207
|
ConfigContext.objects.create(name="Config Context 2", weight=200, data={"bar": 456})
|
|
222
208
|
ConfigContext.objects.create(name="Config Context 3", weight=300, data={"baz": 789})
|
|
209
|
+
cls.create_data = [
|
|
210
|
+
{
|
|
211
|
+
"name": "Config Context 4",
|
|
212
|
+
"data": {"more_foo": True},
|
|
213
|
+
"tags": [tag.pk for tag in Tag.objects.get_for_model(Device)],
|
|
214
|
+
},
|
|
215
|
+
{
|
|
216
|
+
"name": "Config Context 5",
|
|
217
|
+
"data": {"more_bar": False},
|
|
218
|
+
},
|
|
219
|
+
{
|
|
220
|
+
"name": "Config Context 6",
|
|
221
|
+
"data": {"more_baz": None},
|
|
222
|
+
},
|
|
223
|
+
]
|
|
223
224
|
|
|
224
225
|
def test_render_configcontext_for_object(self):
|
|
225
226
|
"""
|
|
@@ -1797,7 +1798,7 @@ class JobTest(
|
|
|
1797
1798
|
)
|
|
1798
1799
|
self.assertHttpStatus(response, status.HTTP_400_BAD_REQUEST)
|
|
1799
1800
|
self.assertIn(
|
|
1800
|
-
"task_queue and job_queue are both specified. Please
|
|
1801
|
+
"task_queue and job_queue are both specified. Please specify only one or another.", str(response.content)
|
|
1801
1802
|
)
|
|
1802
1803
|
|
|
1803
1804
|
@override_settings(EXEMPT_VIEW_PERMISSIONS=["*"])
|
|
@@ -1916,7 +1917,7 @@ class JobTest(
|
|
|
1916
1917
|
mock_get_worker_count.return_value = 1
|
|
1917
1918
|
self.add_permissions("extras.run_job")
|
|
1918
1919
|
|
|
1919
|
-
job_model = Job.objects.get(job_class_name="
|
|
1920
|
+
job_model = Job.objects.get(job_class_name="TestHasSensitiveVariables")
|
|
1920
1921
|
job_model.enabled = True
|
|
1921
1922
|
job_model.validated_save()
|
|
1922
1923
|
|
|
@@ -238,11 +238,7 @@ class GitTest(TransactionTestCase):
|
|
|
238
238
|
repository=self.repo.pk,
|
|
239
239
|
)
|
|
240
240
|
|
|
241
|
-
self.
|
|
242
|
-
job_result.status,
|
|
243
|
-
JobResultStatusChoices.STATUS_FAILURE,
|
|
244
|
-
(job_result.result, list(job_result.job_log_entries.values_list("message", "log_object"))),
|
|
245
|
-
)
|
|
241
|
+
self.assertJobResultStatus(job_result, JobResultStatusChoices.STATUS_FAILURE)
|
|
246
242
|
self.repo.refresh_from_db()
|
|
247
243
|
|
|
248
244
|
log_entries = JobLogEntry.objects.filter(job_result=job_result)
|
|
@@ -308,11 +304,7 @@ class GitTest(TransactionTestCase):
|
|
|
308
304
|
repository=self.repo.pk,
|
|
309
305
|
)
|
|
310
306
|
|
|
311
|
-
self.
|
|
312
|
-
job_result.status,
|
|
313
|
-
JobResultStatusChoices.STATUS_SUCCESS,
|
|
314
|
-
(job_result.traceback, list(job_result.job_log_entries.values_list("message", flat=True))),
|
|
315
|
-
)
|
|
307
|
+
self.assertJobResultStatus(job_result)
|
|
316
308
|
self.repo.refresh_from_db()
|
|
317
309
|
MockGitRepo.assert_called_with(
|
|
318
310
|
os.path.join(tempdir, self.repo.slug),
|
|
@@ -331,11 +323,7 @@ class GitTest(TransactionTestCase):
|
|
|
331
323
|
job_model = GitRepositorySync().job_model
|
|
332
324
|
job_result = run_job_for_testing(job=job_model, repository=self.repo.pk)
|
|
333
325
|
job_result.refresh_from_db()
|
|
334
|
-
self.
|
|
335
|
-
job_result.status,
|
|
336
|
-
JobResultStatusChoices.STATUS_SUCCESS,
|
|
337
|
-
(job_result.traceback, list(job_result.job_log_entries.values_list("message", flat=True))),
|
|
338
|
-
)
|
|
326
|
+
self.assertJobResultStatus(job_result)
|
|
339
327
|
|
|
340
328
|
# Make sure explicit ConfigContext was successfully loaded from file
|
|
341
329
|
self.assert_explicit_config_context_exists("Frobozz 1000 NTP servers")
|
|
@@ -383,11 +371,7 @@ class GitTest(TransactionTestCase):
|
|
|
383
371
|
# Run the Git operation and refresh the object from the DB
|
|
384
372
|
job_result = run_job_for_testing(job=job_model, repository=self.repo.pk)
|
|
385
373
|
job_result.refresh_from_db()
|
|
386
|
-
self.
|
|
387
|
-
job_result.status,
|
|
388
|
-
JobResultStatusChoices.STATUS_SUCCESS,
|
|
389
|
-
(job_result.traceback, list(job_result.job_log_entries.values_list("message", flat=True))),
|
|
390
|
-
)
|
|
374
|
+
self.assertJobResultStatus(job_result)
|
|
391
375
|
|
|
392
376
|
# Verify that objects have been removed from the database
|
|
393
377
|
self.assertEqual(
|
|
@@ -442,11 +426,7 @@ class GitTest(TransactionTestCase):
|
|
|
442
426
|
)
|
|
443
427
|
job_result.refresh_from_db()
|
|
444
428
|
|
|
445
|
-
self.
|
|
446
|
-
job_result.status,
|
|
447
|
-
JobResultStatusChoices.STATUS_FAILURE,
|
|
448
|
-
job_result.result,
|
|
449
|
-
)
|
|
429
|
+
self.assertJobResultStatus(job_result, JobResultStatusChoices.STATUS_FAILURE)
|
|
450
430
|
|
|
451
431
|
# Due to transaction rollback on failure, the database should still/again match the pre-sync state, of
|
|
452
432
|
# no records owned by the repository.
|
|
@@ -547,11 +527,7 @@ class GitTest(TransactionTestCase):
|
|
|
547
527
|
)
|
|
548
528
|
job_result.refresh_from_db()
|
|
549
529
|
|
|
550
|
-
self.
|
|
551
|
-
job_result.status,
|
|
552
|
-
JobResultStatusChoices.STATUS_SUCCESS,
|
|
553
|
-
(job_result.traceback, list(job_result.job_log_entries.values_list("message", flat=True))),
|
|
554
|
-
)
|
|
530
|
+
self.assertJobResultStatus(job_result)
|
|
555
531
|
|
|
556
532
|
# Make sure ConfigContext was successfully loaded from file
|
|
557
533
|
config_context = ConfigContext.objects.get(
|
|
@@ -591,15 +567,7 @@ class GitTest(TransactionTestCase):
|
|
|
591
567
|
delete_job_result = JobResult.objects.filter(name=repo_name).first()
|
|
592
568
|
# Make sure we didn't get the wrong JobResult
|
|
593
569
|
self.assertNotEqual(job_result, delete_job_result)
|
|
594
|
-
self.
|
|
595
|
-
delete_job_result.status,
|
|
596
|
-
JobResultStatusChoices.STATUS_SUCCESS,
|
|
597
|
-
(
|
|
598
|
-
delete_job_result,
|
|
599
|
-
delete_job_result.traceback,
|
|
600
|
-
list(delete_job_result.job_log_entries.values_list("message", flat=True)),
|
|
601
|
-
),
|
|
602
|
-
)
|
|
570
|
+
self.assertJobResultStatus(delete_job_result)
|
|
603
571
|
|
|
604
572
|
with self.assertRaises(ConfigContext.DoesNotExist):
|
|
605
573
|
ConfigContext.objects.get(
|
|
@@ -637,11 +605,7 @@ class GitTest(TransactionTestCase):
|
|
|
637
605
|
job_model = GitRepositorySync().job_model
|
|
638
606
|
job_result = run_job_for_testing(job=job_model, repository=self.repo.pk)
|
|
639
607
|
job_result.refresh_from_db()
|
|
640
|
-
self.
|
|
641
|
-
job_result.status,
|
|
642
|
-
JobResultStatusChoices.STATUS_SUCCESS,
|
|
643
|
-
(job_result.traceback, list(job_result.job_log_entries.values_list("message", flat=True))),
|
|
644
|
-
)
|
|
608
|
+
self.assertJobResultStatus(job_result)
|
|
645
609
|
|
|
646
610
|
self.assert_explicit_config_context_exists("Frobozz 1000 NTP servers")
|
|
647
611
|
self.assert_implicit_config_context_exists("Location context")
|
|
@@ -673,11 +637,7 @@ class GitTest(TransactionTestCase):
|
|
|
673
637
|
# Resync, attempting and failing to update to the new commit
|
|
674
638
|
job_result = run_job_for_testing(job=job_model, repository=self.repo.pk)
|
|
675
639
|
job_result.refresh_from_db()
|
|
676
|
-
self.
|
|
677
|
-
job_result.status,
|
|
678
|
-
JobResultStatusChoices.STATUS_FAILURE,
|
|
679
|
-
job_result.result,
|
|
680
|
-
)
|
|
640
|
+
self.assertJobResultStatus(job_result, JobResultStatusChoices.STATUS_FAILURE)
|
|
681
641
|
log_entries = JobLogEntry.objects.filter(job_result=job_result)
|
|
682
642
|
|
|
683
643
|
# Assert database changes were rolled back
|
|
@@ -718,11 +678,7 @@ class GitTest(TransactionTestCase):
|
|
|
718
678
|
)
|
|
719
679
|
job_result.refresh_from_db()
|
|
720
680
|
|
|
721
|
-
self.
|
|
722
|
-
job_result.status,
|
|
723
|
-
JobResultStatusChoices.STATUS_SUCCESS,
|
|
724
|
-
(job_result.traceback, list(job_result.job_log_entries.values_list("message", flat=True))),
|
|
725
|
-
)
|
|
681
|
+
self.assertJobResultStatus(job_result)
|
|
726
682
|
|
|
727
683
|
log_entries = JobLogEntry.objects.filter(job_result=job_result)
|
|
728
684
|
|
|
@@ -798,3 +754,57 @@ class GitTest(TransactionTestCase):
|
|
|
798
754
|
"provides contents overlapping with this repository.",
|
|
799
755
|
str(cm.exception),
|
|
800
756
|
)
|
|
757
|
+
|
|
758
|
+
@mock.patch("nautobot.extras.models.datasources.GitRepo")
|
|
759
|
+
def test_clone_to_directory_with_secrets(self, MockGitRepo):
|
|
760
|
+
"""
|
|
761
|
+
The clone_to_directory method should correctly make use of secrets.
|
|
762
|
+
"""
|
|
763
|
+
with tempfile.TemporaryDirectory() as tempdir:
|
|
764
|
+
# Prepare secrets values
|
|
765
|
+
with open(os.path.join(tempdir, "username.txt"), "wt") as handle:
|
|
766
|
+
handle.write("núñez")
|
|
767
|
+
|
|
768
|
+
with open(os.path.join(tempdir, "token.txt"), "wt") as handle:
|
|
769
|
+
handle.write("1:3@/?=ab@")
|
|
770
|
+
|
|
771
|
+
# Create secrets and assign
|
|
772
|
+
username_secret = Secret.objects.create(
|
|
773
|
+
name="Git Username",
|
|
774
|
+
provider="text-file",
|
|
775
|
+
parameters={"path": os.path.join(tempdir, "username.txt")},
|
|
776
|
+
)
|
|
777
|
+
token_secret = Secret.objects.create(
|
|
778
|
+
name="Git Token",
|
|
779
|
+
provider="text-file",
|
|
780
|
+
parameters={"path": os.path.join(tempdir, "token.txt")},
|
|
781
|
+
)
|
|
782
|
+
secrets_group = SecretsGroup.objects.create(name="Git Credentials")
|
|
783
|
+
SecretsGroupAssociation.objects.create(
|
|
784
|
+
secret=username_secret,
|
|
785
|
+
secrets_group=secrets_group,
|
|
786
|
+
access_type=SecretsGroupAccessTypeChoices.TYPE_HTTP,
|
|
787
|
+
secret_type=SecretsGroupSecretTypeChoices.TYPE_USERNAME,
|
|
788
|
+
)
|
|
789
|
+
SecretsGroupAssociation.objects.create(
|
|
790
|
+
secret=token_secret,
|
|
791
|
+
secrets_group=secrets_group,
|
|
792
|
+
access_type=SecretsGroupAccessTypeChoices.TYPE_HTTP,
|
|
793
|
+
secret_type=SecretsGroupSecretTypeChoices.TYPE_TOKEN,
|
|
794
|
+
)
|
|
795
|
+
|
|
796
|
+
# Configure GitRepository model
|
|
797
|
+
self.repo.secrets_group = secrets_group
|
|
798
|
+
self.repo.remote_url = "http://localhost/git.git"
|
|
799
|
+
self.repo.save()
|
|
800
|
+
|
|
801
|
+
# Try to clone it
|
|
802
|
+
self.repo.clone_to_directory(tempdir, "main")
|
|
803
|
+
|
|
804
|
+
# Assert that GitRepo was called with proper args
|
|
805
|
+
args, kwargs = MockGitRepo.call_args
|
|
806
|
+
path, from_url = args
|
|
807
|
+
self.assertTrue(path.startswith(os.path.join(tempdir, self.repo.slug)))
|
|
808
|
+
self.assertEqual(from_url, "http://n%C3%BA%C3%B1ez:1%3A3%40%2F%3F%3Dab%40@localhost/git.git")
|
|
809
|
+
self.assertEqual(kwargs["depth"], 0)
|
|
810
|
+
self.assertEqual(kwargs["branch"], "main")
|
|
@@ -880,6 +880,8 @@ class JobFilterSetTestCase(FilterTestCases.FilterTestCase):
|
|
|
880
880
|
generic_filter_tests = (
|
|
881
881
|
("grouping",),
|
|
882
882
|
("job_class_name",),
|
|
883
|
+
("job_queues", "job_queues__id"),
|
|
884
|
+
("job_queues", "job_queues__name"),
|
|
883
885
|
("module_name",),
|
|
884
886
|
("name",),
|
|
885
887
|
)
|