netbox-atlassian 0.1.0__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.
@@ -0,0 +1,306 @@
1
+ """
2
+ Views for NetBox Atlassian Plugin
3
+
4
+ Registers custom tabs on Device detail views to show Jira issues and Confluence pages.
5
+ Provides settings configuration UI.
6
+ """
7
+
8
+ import re
9
+
10
+ from dcim.models import Device
11
+ from django.conf import settings
12
+ from django.contrib import messages
13
+ from django.http import JsonResponse
14
+ from django.shortcuts import render
15
+ from django.views import View
16
+ from netbox.views import generic
17
+ from utilities.views import ViewTab, register_model_view
18
+ from virtualization.models import VirtualMachine
19
+
20
+ from .atlassian_client import get_client
21
+ from .forms import AtlassianSettingsForm
22
+
23
+
24
+ def get_device_attribute(device, attribute_path: str):
25
+ """
26
+ Get a device attribute by dot-separated path.
27
+
28
+ Args:
29
+ device: Device instance
30
+ attribute_path: Dot-separated attribute path (e.g., "role.name", "primary_ip4.address")
31
+
32
+ Returns:
33
+ Attribute value or None if not found
34
+ """
35
+ try:
36
+ value = device
37
+ for part in attribute_path.split("."):
38
+ value = getattr(value, part, None)
39
+ if value is None:
40
+ return None
41
+ # Convert to string for IP addresses
42
+ if hasattr(value, "ip"):
43
+ return str(value.ip)
44
+ return str(value) if value else None
45
+ except Exception:
46
+ return None
47
+
48
+
49
+ def get_search_terms(device) -> list[str]:
50
+ """
51
+ Get search terms from device based on configured search fields.
52
+
53
+ Returns list of non-empty values from enabled search fields.
54
+ """
55
+ config = settings.PLUGINS_CONFIG.get("netbox_atlassian", {})
56
+ search_fields = config.get("search_fields", [])
57
+
58
+ terms = []
59
+ for field in search_fields:
60
+ if not field.get("enabled", True):
61
+ continue
62
+
63
+ attribute = field.get("attribute", "")
64
+ if not attribute:
65
+ continue
66
+
67
+ value = get_device_attribute(device, attribute)
68
+ if value and value.strip():
69
+ terms.append(value.strip())
70
+
71
+ return terms
72
+
73
+
74
+ def should_show_atlassian_tab(device) -> bool:
75
+ """
76
+ Determine if the Atlassian tab should be visible for this device.
77
+
78
+ Shows tab if:
79
+ - Device has at least one searchable field value
80
+ - Device type matches configured filters (if any)
81
+ """
82
+ config = settings.PLUGINS_CONFIG.get("netbox_atlassian", {})
83
+ device_types = config.get("device_types", [])
84
+
85
+ # Check device type filter if configured
86
+ if device_types and device.device_type:
87
+ manufacturer_slug = device.device_type.manufacturer.slug.lower() if device.device_type.manufacturer else ""
88
+ manufacturer_name = device.device_type.manufacturer.name.lower() if device.device_type.manufacturer else ""
89
+
90
+ matches = False
91
+ for pattern in device_types:
92
+ try:
93
+ if re.search(pattern.lower(), manufacturer_slug) or re.search(pattern.lower(), manufacturer_name):
94
+ matches = True
95
+ break
96
+ except re.error:
97
+ if pattern.lower() in manufacturer_slug or pattern.lower() in manufacturer_name:
98
+ matches = True
99
+ break
100
+
101
+ if not matches:
102
+ return False
103
+
104
+ # Check if we have any search terms
105
+ terms = get_search_terms(device)
106
+ return len(terms) > 0
107
+
108
+
109
+ @register_model_view(Device, name="atlassian", path="atlassian")
110
+ class DeviceAtlassianView(generic.ObjectView):
111
+ """Display Jira issues and Confluence pages for a Device."""
112
+
113
+ queryset = Device.objects.all()
114
+ template_name = "netbox_atlassian/device_tab.html"
115
+
116
+ tab = ViewTab(
117
+ label="Atlassian",
118
+ weight=9100,
119
+ permission="dcim.view_device",
120
+ hide_if_empty=False,
121
+ )
122
+
123
+ def get(self, request, pk):
124
+ """Handle GET request for the Atlassian tab."""
125
+ device = Device.objects.get(pk=pk)
126
+
127
+ config = settings.PLUGINS_CONFIG.get("netbox_atlassian", {})
128
+ client = get_client()
129
+
130
+ # Get search terms from device
131
+ search_terms = get_search_terms(device)
132
+
133
+ # Get configured search fields for display
134
+ search_fields = config.get("search_fields", [])
135
+ enabled_fields = [f for f in search_fields if f.get("enabled", True)]
136
+
137
+ # Search Jira and Confluence
138
+ jira_results = {"issues": [], "total": 0, "error": None}
139
+ confluence_results = {"pages": [], "total": 0, "error": None}
140
+
141
+ if search_terms:
142
+ jira_max = config.get("jira_max_results", 10)
143
+ confluence_max = config.get("confluence_max_results", 10)
144
+
145
+ jira_results = client.search_jira(search_terms, max_results=jira_max)
146
+ confluence_results = client.search_confluence(search_terms, max_results=confluence_max)
147
+
148
+ # Get URLs for external links
149
+ jira_url = config.get("jira_url", "").rstrip("/")
150
+ confluence_url = config.get("confluence_url", "").rstrip("/")
151
+
152
+ return render(
153
+ request,
154
+ self.template_name,
155
+ {
156
+ "object": device,
157
+ "tab": self.tab,
158
+ "search_terms": search_terms,
159
+ "enabled_fields": enabled_fields,
160
+ "jira_results": jira_results,
161
+ "confluence_results": confluence_results,
162
+ "jira_url": jira_url,
163
+ "confluence_url": confluence_url,
164
+ "jira_configured": bool(jira_url),
165
+ "confluence_configured": bool(confluence_url),
166
+ },
167
+ )
168
+
169
+
170
+ @register_model_view(VirtualMachine, name="atlassian", path="atlassian")
171
+ class VirtualMachineAtlassianView(generic.ObjectView):
172
+ """Display Jira issues and Confluence pages for a VirtualMachine."""
173
+
174
+ queryset = VirtualMachine.objects.all()
175
+ template_name = "netbox_atlassian/vm_tab.html"
176
+
177
+ tab = ViewTab(
178
+ label="Atlassian",
179
+ weight=9100,
180
+ permission="virtualization.view_virtualmachine",
181
+ hide_if_empty=False,
182
+ )
183
+
184
+ def get(self, request, pk):
185
+ """Handle GET request for the Atlassian tab."""
186
+ vm = VirtualMachine.objects.get(pk=pk)
187
+
188
+ config = settings.PLUGINS_CONFIG.get("netbox_atlassian", {})
189
+ client = get_client()
190
+
191
+ # For VMs, search by name and primary IP
192
+ search_terms = []
193
+ if vm.name:
194
+ search_terms.append(vm.name)
195
+ if vm.primary_ip4:
196
+ search_terms.append(str(vm.primary_ip4.address.ip))
197
+
198
+ # Get configured search fields for display
199
+ search_fields = config.get("search_fields", [])
200
+ enabled_fields = [{"name": "Name", "attribute": "name", "enabled": True}]
201
+ if vm.primary_ip4:
202
+ enabled_fields.append({"name": "Primary IP", "attribute": "primary_ip4", "enabled": True})
203
+
204
+ # Search Jira and Confluence
205
+ jira_results = {"issues": [], "total": 0, "error": None}
206
+ confluence_results = {"pages": [], "total": 0, "error": None}
207
+
208
+ if search_terms:
209
+ jira_max = config.get("jira_max_results", 10)
210
+ confluence_max = config.get("confluence_max_results", 10)
211
+
212
+ jira_results = client.search_jira(search_terms, max_results=jira_max)
213
+ confluence_results = client.search_confluence(search_terms, max_results=confluence_max)
214
+
215
+ # Get URLs for external links
216
+ jira_url = config.get("jira_url", "").rstrip("/")
217
+ confluence_url = config.get("confluence_url", "").rstrip("/")
218
+
219
+ return render(
220
+ request,
221
+ self.template_name,
222
+ {
223
+ "object": vm,
224
+ "tab": self.tab,
225
+ "search_terms": search_terms,
226
+ "enabled_fields": enabled_fields,
227
+ "jira_results": jira_results,
228
+ "confluence_results": confluence_results,
229
+ "jira_url": jira_url,
230
+ "confluence_url": confluence_url,
231
+ "jira_configured": bool(jira_url),
232
+ "confluence_configured": bool(confluence_url),
233
+ },
234
+ )
235
+
236
+
237
+ class AtlassianSettingsView(View):
238
+ """View for configuring Atlassian plugin settings."""
239
+
240
+ template_name = "netbox_atlassian/settings.html"
241
+
242
+ def get_current_config(self):
243
+ """Get current plugin configuration."""
244
+ return settings.PLUGINS_CONFIG.get("netbox_atlassian", {})
245
+
246
+ def get(self, request):
247
+ """Display the settings form."""
248
+ config = self.get_current_config()
249
+ form = AtlassianSettingsForm(initial=config)
250
+
251
+ return render(
252
+ request,
253
+ self.template_name,
254
+ {
255
+ "form": form,
256
+ "config": config,
257
+ },
258
+ )
259
+
260
+ def post(self, request):
261
+ """Handle settings form submission."""
262
+ form = AtlassianSettingsForm(request.POST)
263
+
264
+ if form.is_valid():
265
+ messages.warning(
266
+ request,
267
+ "Settings must be configured in NetBox's configuration.py file. "
268
+ "See the README for configuration instructions.",
269
+ )
270
+ else:
271
+ messages.error(request, "Invalid settings provided.")
272
+
273
+ return render(
274
+ request,
275
+ self.template_name,
276
+ {
277
+ "form": form,
278
+ "config": self.get_current_config(),
279
+ },
280
+ )
281
+
282
+
283
+ class TestJiraConnectionView(View):
284
+ """Test connection to Jira API."""
285
+
286
+ def post(self, request):
287
+ """Test the Jira connection and return result."""
288
+ client = get_client()
289
+ success, message = client.test_jira_connection()
290
+
291
+ if success:
292
+ return JsonResponse({"success": True, "message": message})
293
+ return JsonResponse({"success": False, "error": message}, status=400)
294
+
295
+
296
+ class TestConfluenceConnectionView(View):
297
+ """Test connection to Confluence API."""
298
+
299
+ def post(self, request):
300
+ """Test the Confluence connection and return result."""
301
+ client = get_client()
302
+ success, message = client.test_confluence_connection()
303
+
304
+ if success:
305
+ return JsonResponse({"success": True, "message": message})
306
+ return JsonResponse({"success": False, "error": message}, status=400)
@@ -0,0 +1,186 @@
1
+ Metadata-Version: 2.4
2
+ Name: netbox-atlassian
3
+ Version: 0.1.0
4
+ Summary: NetBox plugin to display Jira issues and Confluence pages related to devices
5
+ Author-email: sieteunoseis <jeremy.worden@gmail.com>
6
+ License: Apache-2.0
7
+ Project-URL: Homepage, https://github.com/sieteunoseis/netbox-atlassian
8
+ Project-URL: Repository, https://github.com/sieteunoseis/netbox-atlassian
9
+ Project-URL: Issues, https://github.com/sieteunoseis/netbox-atlassian/issues
10
+ Keywords: netbox,jira,confluence,atlassian,plugin
11
+ Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Framework :: Django
13
+ Classifier: Intended Audience :: System Administrators
14
+ Classifier: License :: OSI Approved :: Apache Software License
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.10
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Requires-Python: >=3.10
20
+ Description-Content-Type: text/markdown
21
+ Requires-Dist: requests>=2.28.0
22
+ Provides-Extra: dev
23
+ Requires-Dist: black; extra == "dev"
24
+ Requires-Dist: flake8; extra == "dev"
25
+ Requires-Dist: isort; extra == "dev"
26
+ Requires-Dist: pytest; extra == "dev"
27
+
28
+ # NetBox Atlassian Plugin
29
+
30
+ Display Jira issues and Confluence pages related to devices in NetBox.
31
+
32
+ ## Features
33
+
34
+ - **Device Tab** - Shows Jira issues and Confluence pages mentioning device attributes
35
+ - **VM Tab** - Same functionality for Virtual Machines
36
+ - **Configurable Search Fields** - Search by hostname, serial, asset tag, role, IP, etc.
37
+ - **OR Search Logic** - Finds content matching any configured field
38
+ - **On-Premise Support** - Works with Jira Server/Data Center and Confluence Server/Data Center
39
+ - **Cloud Ready** - Prepared for Atlassian Cloud (future release)
40
+ - **Caching** - Results cached to reduce API calls
41
+ - **Project/Space Filtering** - Limit searches to specific Jira projects or Confluence spaces
42
+
43
+ ## Requirements
44
+
45
+ - NetBox 4.0+
46
+ - Python 3.10+
47
+ - Access to Jira and/or Confluence REST APIs
48
+
49
+ ## Installation
50
+
51
+ ```bash
52
+ pip install netbox-atlassian
53
+ ```
54
+
55
+ Add to `configuration.py`:
56
+
57
+ ```python
58
+ PLUGINS = [
59
+ "netbox_atlassian",
60
+ ]
61
+ ```
62
+
63
+ ## Configuration
64
+
65
+ ```python
66
+ PLUGINS_CONFIG = {
67
+ "netbox_atlassian": {
68
+ # Jira settings (on-prem)
69
+ "jira_url": "https://jira.example.com",
70
+ "jira_username": "api_user",
71
+ "jira_password": "api_token_or_password",
72
+ "jira_verify_ssl": True,
73
+ "jira_projects": [], # Empty = all projects
74
+
75
+ # Confluence settings (on-prem)
76
+ "confluence_url": "https://confluence.example.com",
77
+ "confluence_username": "api_user",
78
+ "confluence_password": "api_token_or_password",
79
+ "confluence_verify_ssl": True,
80
+ "confluence_spaces": [], # Empty = all spaces
81
+
82
+ # Search configuration
83
+ "search_fields": [
84
+ {"name": "Hostname", "attribute": "name", "enabled": True},
85
+ {"name": "Serial", "attribute": "serial", "enabled": True},
86
+ {"name": "Asset Tag", "attribute": "asset_tag", "enabled": False},
87
+ {"name": "Role", "attribute": "role.name", "enabled": False},
88
+ {"name": "Primary IP", "attribute": "primary_ip4.address", "enabled": False},
89
+ ],
90
+
91
+ # Results limits
92
+ "jira_max_results": 10,
93
+ "confluence_max_results": 10,
94
+
95
+ # General
96
+ "timeout": 30,
97
+ "cache_timeout": 300,
98
+ "device_types": [], # Filter by manufacturer (empty = all)
99
+ }
100
+ }
101
+ ```
102
+
103
+ ## Search Fields
104
+
105
+ The `search_fields` configuration defines which device attributes are searched. Searches use **OR logic** - content matching any enabled field will be returned.
106
+
107
+ | Field | Attribute Path | Description |
108
+ |-------|---------------|-------------|
109
+ | Hostname | `name` | Device name |
110
+ | Serial | `serial` | Serial number |
111
+ | Asset Tag | `asset_tag` | Asset tag |
112
+ | Role | `role.name` | Device role name |
113
+ | Primary IP | `primary_ip4.address` | Primary IPv4 address |
114
+ | Site | `site.name` | Site name |
115
+ | Tenant | `tenant.name` | Tenant name |
116
+
117
+ ### Custom Fields
118
+
119
+ You can search custom fields using dot notation:
120
+
121
+ ```python
122
+ {"name": "CMDB ID", "attribute": "custom_field_data.cmdb_id", "enabled": True}
123
+ ```
124
+
125
+ ## Screenshots
126
+
127
+ ### Device Tab
128
+ Shows Jira issues and Confluence pages in a split view:
129
+ - Left: Jira issues with key, summary, status, and type
130
+ - Right: Confluence pages with title, space, and breadcrumb
131
+
132
+ ### Settings Page
133
+ View current configuration and test connections to Jira/Confluence.
134
+
135
+ ## API Endpoints
136
+
137
+ The plugin adds these endpoints:
138
+
139
+ | Endpoint | Method | Description |
140
+ |----------|--------|-------------|
141
+ | `/plugins/atlassian/settings/` | GET | View settings |
142
+ | `/plugins/atlassian/test-jira/` | POST | Test Jira connection |
143
+ | `/plugins/atlassian/test-confluence/` | POST | Test Confluence connection |
144
+
145
+ ## Troubleshooting
146
+
147
+ ### Connection Errors
148
+
149
+ 1. Verify URLs are correct and accessible from NetBox server
150
+ 2. Check username/password or API token
151
+ 3. For on-prem, ensure `verify_ssl` matches your certificate setup
152
+ 4. Check firewall rules allow outbound HTTPS
153
+
154
+ ### No Results
155
+
156
+ 1. Verify search fields are enabled in configuration
157
+ 2. Check that Jira/Confluence content contains the device attributes
158
+ 3. Review project/space filters if configured
159
+ 4. Check API user has read permissions
160
+
161
+ ### Slow Performance
162
+
163
+ 1. Increase `cache_timeout` to reduce API calls
164
+ 2. Reduce `max_results` values
165
+ 3. Use project/space filters to limit search scope
166
+
167
+ ## Development
168
+
169
+ ```bash
170
+ git clone https://github.com/sieteunoseis/netbox-atlassian.git
171
+ cd netbox-atlassian
172
+ pip install -e ".[dev]"
173
+
174
+ # Run linting
175
+ black netbox_atlassian/
176
+ isort netbox_atlassian/
177
+ flake8 netbox_atlassian/
178
+ ```
179
+
180
+ ## License
181
+
182
+ Apache 2.0
183
+
184
+ ## Author
185
+
186
+ sieteunoseis
@@ -0,0 +1,13 @@
1
+ netbox_atlassian/__init__.py,sha256=caHvJRtHTHUx1tRKQyNXzYvMihUJ71fFWkJPqgunTIs,2754
2
+ netbox_atlassian/atlassian_client.py,sha256=oO4maq4AWknbf-HEin72BS30gM7HcRsunC0xv9O9YUI,12859
3
+ netbox_atlassian/forms.py,sha256=b8MKsU3Ou-c0s7Cga_72m7E3UCCliZF-17woybN66dk,3385
4
+ netbox_atlassian/navigation.py,sha256=tpRgS8qESmxm5eEuCOd1UjEVXEDTMM12H4lEaZk1BRw,497
5
+ netbox_atlassian/urls.py,sha256=kIiL-G8wfMd1tdSrnWeWcTuHZMx6CCc5mLr_WjKn8Rs,454
6
+ netbox_atlassian/views.py,sha256=EQlU2hcHNcRfwsD5C73L1X5u82WeB26XqSPcMVxl9S4,10118
7
+ netbox_atlassian/templates/netbox_atlassian/device_tab.html,sha256=Vs1QMlCa6vcWF0HrL1VlV6mHrl9iaCC_R3IsjH1BcJQ,9521
8
+ netbox_atlassian/templates/netbox_atlassian/settings.html,sha256=bmiIzzJ6m1vwT5AOcOrdJ7uD3V31GbKUpT4M6TIP3wE,11709
9
+ netbox_atlassian/templates/netbox_atlassian/vm_tab.html,sha256=Q_Psv-08lyLys_CAF4oJvAUIWcg_2OnkcMFbdnKg8vk,9539
10
+ netbox_atlassian-0.1.0.dist-info/METADATA,sha256=Zw0phWkrxAmDEXe0oySURhi5xLQ7RRBamQcpXqDIlfY,5675
11
+ netbox_atlassian-0.1.0.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
12
+ netbox_atlassian-0.1.0.dist-info/top_level.txt,sha256=VaKVLTW0o2SE7896PzRTWUaEeYt8CWsryBXI6Ij4ECg,17
13
+ netbox_atlassian-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (80.10.2)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1 @@
1
+ netbox_atlassian