django-cfg 1.2.15__py3-none-any.whl → 1.2.16__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.
Files changed (61) hide show
  1. django_cfg/__init__.py +1 -1
  2. django_cfg/apps/maintenance/README.md +305 -0
  3. django_cfg/apps/maintenance/__init__.py +27 -0
  4. django_cfg/apps/maintenance/admin/__init__.py +28 -0
  5. django_cfg/apps/maintenance/admin/deployments_admin.py +251 -0
  6. django_cfg/apps/maintenance/admin/events_admin.py +374 -0
  7. django_cfg/apps/maintenance/admin/monitoring_admin.py +215 -0
  8. django_cfg/apps/maintenance/admin/sites_admin.py +464 -0
  9. django_cfg/apps/maintenance/apps.py +105 -0
  10. django_cfg/apps/maintenance/management/__init__.py +0 -0
  11. django_cfg/apps/maintenance/management/commands/__init__.py +0 -0
  12. django_cfg/apps/maintenance/management/commands/maintenance.py +375 -0
  13. django_cfg/apps/maintenance/management/commands/sync_cloudflare.py +168 -0
  14. django_cfg/apps/maintenance/managers/__init__.py +20 -0
  15. django_cfg/apps/maintenance/managers/deployments.py +287 -0
  16. django_cfg/apps/maintenance/managers/events.py +374 -0
  17. django_cfg/apps/maintenance/managers/monitoring.py +301 -0
  18. django_cfg/apps/maintenance/managers/sites.py +335 -0
  19. django_cfg/apps/maintenance/migrations/0001_initial.py +939 -0
  20. django_cfg/apps/maintenance/migrations/__init__.py +0 -0
  21. django_cfg/apps/maintenance/models/__init__.py +27 -0
  22. django_cfg/apps/maintenance/models/cloudflare.py +316 -0
  23. django_cfg/apps/maintenance/models/maintenance.py +334 -0
  24. django_cfg/apps/maintenance/models/monitoring.py +393 -0
  25. django_cfg/apps/maintenance/models/sites.py +419 -0
  26. django_cfg/apps/maintenance/serializers/__init__.py +60 -0
  27. django_cfg/apps/maintenance/serializers/actions.py +310 -0
  28. django_cfg/apps/maintenance/serializers/base.py +44 -0
  29. django_cfg/apps/maintenance/serializers/deployments.py +209 -0
  30. django_cfg/apps/maintenance/serializers/events.py +210 -0
  31. django_cfg/apps/maintenance/serializers/monitoring.py +278 -0
  32. django_cfg/apps/maintenance/serializers/sites.py +213 -0
  33. django_cfg/apps/maintenance/services/README.md +168 -0
  34. django_cfg/apps/maintenance/services/__init__.py +21 -0
  35. django_cfg/apps/maintenance/services/cloudflare_client.py +441 -0
  36. django_cfg/apps/maintenance/services/dns_manager.py +497 -0
  37. django_cfg/apps/maintenance/services/maintenance_manager.py +504 -0
  38. django_cfg/apps/maintenance/services/site_sync.py +448 -0
  39. django_cfg/apps/maintenance/services/sync_command_service.py +330 -0
  40. django_cfg/apps/maintenance/services/worker_manager.py +264 -0
  41. django_cfg/apps/maintenance/signals.py +38 -0
  42. django_cfg/apps/maintenance/urls.py +36 -0
  43. django_cfg/apps/maintenance/views/__init__.py +18 -0
  44. django_cfg/apps/maintenance/views/base.py +61 -0
  45. django_cfg/apps/maintenance/views/deployments.py +175 -0
  46. django_cfg/apps/maintenance/views/events.py +204 -0
  47. django_cfg/apps/maintenance/views/monitoring.py +213 -0
  48. django_cfg/apps/maintenance/views/sites.py +338 -0
  49. django_cfg/apps/urls.py +5 -1
  50. django_cfg/core/config.py +34 -3
  51. django_cfg/core/generation.py +15 -10
  52. django_cfg/models/cloudflare.py +316 -0
  53. django_cfg/models/revolution.py +1 -1
  54. django_cfg/models/tasks.py +1 -1
  55. django_cfg/modules/base.py +12 -5
  56. django_cfg/modules/django_unfold/dashboard.py +16 -1
  57. {django_cfg-1.2.15.dist-info → django_cfg-1.2.16.dist-info}/METADATA +2 -1
  58. {django_cfg-1.2.15.dist-info → django_cfg-1.2.16.dist-info}/RECORD +61 -13
  59. {django_cfg-1.2.15.dist-info → django_cfg-1.2.16.dist-info}/WHEEL +0 -0
  60. {django_cfg-1.2.15.dist-info → django_cfg-1.2.16.dist-info}/entry_points.txt +0 -0
  61. {django_cfg-1.2.15.dist-info → django_cfg-1.2.16.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,497 @@
1
+ """
2
+ DNS management service using official Cloudflare library.
3
+
4
+ Provides high-level DNS operations with validation and bulk operations.
5
+ """
6
+
7
+ import logging
8
+ from typing import Dict, List, Optional, Any, Tuple
9
+ from datetime import datetime
10
+
11
+ from .cloudflare_client import CloudflareClient
12
+ from django_cfg.models.cloudflare import CloudflareConfig
13
+
14
+ logger = logging.getLogger(__name__)
15
+
16
+
17
+ class DNSManager:
18
+ """
19
+ DNS management service for Cloudflare.
20
+
21
+ Provides high-level DNS operations with validation and error handling.
22
+ """
23
+
24
+ # Common DNS record types and their validation
25
+ RECORD_TYPES = {
26
+ 'A': {'requires_ip': True, 'supports_proxy': True},
27
+ 'AAAA': {'requires_ip': True, 'supports_proxy': True},
28
+ 'CNAME': {'requires_domain': True, 'supports_proxy': True},
29
+ 'MX': {'requires_priority': True, 'supports_proxy': False},
30
+ 'TXT': {'supports_proxy': False},
31
+ 'NS': {'supports_proxy': False},
32
+ 'SRV': {'requires_priority': True, 'supports_proxy': False},
33
+ 'CAA': {'supports_proxy': False},
34
+ 'PTR': {'supports_proxy': False}
35
+ }
36
+
37
+ def __init__(self, config: CloudflareConfig):
38
+ """Initialize DNS manager."""
39
+ self.config = config
40
+ self.client = CloudflareClient(config)
41
+
42
+ def create_dns_record(
43
+ self,
44
+ zone_id: str,
45
+ record_type: str,
46
+ name: str,
47
+ content: str,
48
+ ttl: int = 300,
49
+ proxied: Optional[bool] = None,
50
+ priority: Optional[int] = None,
51
+ validate: bool = True
52
+ ) -> Dict[str, Any]:
53
+ """
54
+ Create a DNS record with validation.
55
+
56
+ Args:
57
+ zone_id: Cloudflare zone ID
58
+ record_type: DNS record type (A, CNAME, etc.)
59
+ name: Record name
60
+ content: Record content
61
+ ttl: Time to live (300-86400)
62
+ proxied: Whether to proxy through Cloudflare
63
+ priority: Priority for MX/SRV records
64
+ validate: Whether to validate record before creation
65
+
66
+ Returns:
67
+ Dict with creation results
68
+ """
69
+ logger.info(f"Creating DNS record: {record_type} {name}")
70
+
71
+ try:
72
+ # Validate record if requested
73
+ if validate:
74
+ validation_result = self._validate_record(
75
+ record_type, name, content, ttl, proxied, priority
76
+ )
77
+ if not validation_result['valid']:
78
+ return {
79
+ 'success': False,
80
+ 'error': f"Validation failed: {validation_result['error']}",
81
+ 'record_type': record_type,
82
+ 'name': name
83
+ }
84
+
85
+ # Determine proxied setting
86
+ if proxied is None:
87
+ proxied = self._should_proxy_record(record_type)
88
+
89
+ # Create the record
90
+ record = self.client.create_dns_record(
91
+ zone_id=zone_id,
92
+ record_type=record_type,
93
+ name=name,
94
+ content=content,
95
+ ttl=ttl,
96
+ proxied=proxied,
97
+ priority=priority
98
+ )
99
+
100
+ logger.info(f"Successfully created DNS record: {record.id}")
101
+
102
+ return {
103
+ 'success': True,
104
+ 'record_id': record.id,
105
+ 'record_type': record_type,
106
+ 'name': name,
107
+ 'content': content,
108
+ 'ttl': ttl,
109
+ 'proxied': proxied,
110
+ 'created_at': datetime.now().isoformat()
111
+ }
112
+
113
+ except Exception as e:
114
+ logger.error(f"Failed to create DNS record {name}: {e}")
115
+ return {
116
+ 'success': False,
117
+ 'error': str(e),
118
+ 'record_type': record_type,
119
+ 'name': name
120
+ }
121
+
122
+ def bulk_create_records(
123
+ self,
124
+ zone_id: str,
125
+ records: List[Dict[str, Any]],
126
+ validate_all: bool = True,
127
+ stop_on_error: bool = False
128
+ ) -> Dict[str, Any]:
129
+ """
130
+ Create multiple DNS records in bulk.
131
+
132
+ Args:
133
+ zone_id: Cloudflare zone ID
134
+ records: List of record configurations
135
+ validate_all: Whether to validate all records before creating any
136
+ stop_on_error: Whether to stop on first error
137
+
138
+ Returns:
139
+ Dict with bulk operation results
140
+ """
141
+ logger.info(f"Creating {len(records)} DNS records in bulk")
142
+
143
+ results = {
144
+ 'success': 0,
145
+ 'failed': 0,
146
+ 'records': [],
147
+ 'errors': []
148
+ }
149
+
150
+ # Validate all records first if requested
151
+ if validate_all:
152
+ for i, record_config in enumerate(records):
153
+ validation = self._validate_record_config(record_config)
154
+ if not validation['valid']:
155
+ error_msg = f"Record {i+1} validation failed: {validation['error']}"
156
+ logger.error(error_msg)
157
+ results['errors'].append(error_msg)
158
+
159
+ if stop_on_error:
160
+ return {
161
+ 'success': False,
162
+ 'error': error_msg,
163
+ 'results': results
164
+ }
165
+
166
+ # Create records
167
+ for i, record_config in enumerate(records):
168
+ try:
169
+ result = self.create_dns_record(
170
+ zone_id=zone_id,
171
+ validate=not validate_all, # Skip individual validation if we validated all
172
+ **record_config
173
+ )
174
+
175
+ if result['success']:
176
+ results['success'] += 1
177
+ else:
178
+ results['failed'] += 1
179
+ if stop_on_error:
180
+ return {
181
+ 'success': False,
182
+ 'error': result['error'],
183
+ 'results': results
184
+ }
185
+
186
+ results['records'].append({
187
+ 'index': i,
188
+ 'config': record_config,
189
+ 'result': result
190
+ })
191
+
192
+ except Exception as e:
193
+ error_msg = f"Record {i+1} creation failed: {e}"
194
+ logger.error(error_msg)
195
+ results['failed'] += 1
196
+ results['errors'].append(error_msg)
197
+ results['records'].append({
198
+ 'index': i,
199
+ 'config': record_config,
200
+ 'result': {'success': False, 'error': str(e)}
201
+ })
202
+
203
+ if stop_on_error:
204
+ return {
205
+ 'success': False,
206
+ 'error': error_msg,
207
+ 'results': results
208
+ }
209
+
210
+ logger.info(f"Bulk DNS creation completed: {results['success']} success, {results['failed']} failed")
211
+
212
+ return {
213
+ 'success': results['failed'] == 0,
214
+ 'results': results
215
+ }
216
+
217
+ def update_dns_record(
218
+ self,
219
+ zone_id: str,
220
+ record_id: str,
221
+ **kwargs
222
+ ) -> Dict[str, Any]:
223
+ """
224
+ Update an existing DNS record.
225
+
226
+ Args:
227
+ zone_id: Cloudflare zone ID
228
+ record_id: DNS record ID to update
229
+ **kwargs: Fields to update
230
+
231
+ Returns:
232
+ Dict with update results
233
+ """
234
+ logger.info(f"Updating DNS record: {record_id}")
235
+
236
+ try:
237
+ record = self.client.update_dns_record(
238
+ zone_id=zone_id,
239
+ record_id=record_id,
240
+ **kwargs
241
+ )
242
+
243
+ logger.info(f"Successfully updated DNS record: {record_id}")
244
+
245
+ return {
246
+ 'success': True,
247
+ 'record_id': record_id,
248
+ 'updated_at': datetime.now().isoformat()
249
+ }
250
+
251
+ except Exception as e:
252
+ logger.error(f"Failed to update DNS record {record_id}: {e}")
253
+ return {
254
+ 'success': False,
255
+ 'error': str(e),
256
+ 'record_id': record_id
257
+ }
258
+
259
+ def delete_dns_record(
260
+ self,
261
+ zone_id: str,
262
+ record_id: str
263
+ ) -> Dict[str, Any]:
264
+ """
265
+ Delete a DNS record.
266
+
267
+ Args:
268
+ zone_id: Cloudflare zone ID
269
+ record_id: DNS record ID to delete
270
+
271
+ Returns:
272
+ Dict with deletion results
273
+ """
274
+ logger.info(f"Deleting DNS record: {record_id}")
275
+
276
+ try:
277
+ success = self.client.delete_dns_record(zone_id, record_id)
278
+
279
+ if success:
280
+ logger.info(f"Successfully deleted DNS record: {record_id}")
281
+ return {
282
+ 'success': True,
283
+ 'record_id': record_id,
284
+ 'deleted_at': datetime.now().isoformat()
285
+ }
286
+ else:
287
+ return {
288
+ 'success': False,
289
+ 'error': 'Delete operation returned False',
290
+ 'record_id': record_id
291
+ }
292
+
293
+ except Exception as e:
294
+ logger.error(f"Failed to delete DNS record {record_id}: {e}")
295
+ return {
296
+ 'success': False,
297
+ 'error': str(e),
298
+ 'record_id': record_id
299
+ }
300
+
301
+ def get_dns_records(
302
+ self,
303
+ zone_id: str,
304
+ record_type: Optional[str] = None,
305
+ name: Optional[str] = None
306
+ ) -> Dict[str, Any]:
307
+ """
308
+ Get DNS records for a zone.
309
+
310
+ Args:
311
+ zone_id: Cloudflare zone ID
312
+ record_type: Filter by record type
313
+ name: Filter by record name
314
+
315
+ Returns:
316
+ Dict with DNS records
317
+ """
318
+ logger.info(f"Getting DNS records for zone: {zone_id}")
319
+
320
+ try:
321
+ records = self.client.list_dns_records(
322
+ zone_id=zone_id,
323
+ record_type=record_type,
324
+ name=name
325
+ )
326
+
327
+ records_data = []
328
+ for record in records:
329
+ records_data.append({
330
+ 'id': record.id,
331
+ 'type': record.type,
332
+ 'name': record.name,
333
+ 'content': record.content,
334
+ 'ttl': getattr(record, 'ttl', 300),
335
+ 'proxied': getattr(record, 'proxied', False),
336
+ 'created_on': getattr(record, 'created_on', ''),
337
+ 'modified_on': getattr(record, 'modified_on', ''),
338
+ 'priority': getattr(record, 'priority', None)
339
+ })
340
+
341
+ logger.info(f"Found {len(records_data)} DNS records")
342
+
343
+ return {
344
+ 'success': True,
345
+ 'records': records_data,
346
+ 'count': len(records_data)
347
+ }
348
+
349
+ except Exception as e:
350
+ logger.error(f"Failed to get DNS records: {e}")
351
+ return {
352
+ 'success': False,
353
+ 'error': str(e),
354
+ 'records': [],
355
+ 'count': 0
356
+ }
357
+
358
+ def setup_common_records(
359
+ self,
360
+ zone_id: str,
361
+ domain: str,
362
+ server_ip: str,
363
+ mail_server: Optional[str] = None,
364
+ include_www: bool = True
365
+ ) -> Dict[str, Any]:
366
+ """
367
+ Set up common DNS records for a domain.
368
+
369
+ Args:
370
+ zone_id: Cloudflare zone ID
371
+ domain: Domain name
372
+ server_ip: Server IP address
373
+ mail_server: Mail server hostname
374
+ include_www: Whether to include www CNAME
375
+
376
+ Returns:
377
+ Dict with setup results
378
+ """
379
+ logger.info(f"Setting up common DNS records for: {domain}")
380
+
381
+ records_to_create = [
382
+ {
383
+ 'record_type': 'A',
384
+ 'name': domain,
385
+ 'content': server_ip,
386
+ 'proxied': True
387
+ }
388
+ ]
389
+
390
+ if include_www:
391
+ records_to_create.append({
392
+ 'record_type': 'CNAME',
393
+ 'name': f'www.{domain}',
394
+ 'content': domain,
395
+ 'proxied': True
396
+ })
397
+
398
+ if mail_server:
399
+ records_to_create.extend([
400
+ {
401
+ 'record_type': 'MX',
402
+ 'name': domain,
403
+ 'content': mail_server,
404
+ 'priority': 10,
405
+ 'proxied': False
406
+ },
407
+ {
408
+ 'record_type': 'CNAME',
409
+ 'name': f'mail.{domain}',
410
+ 'content': mail_server,
411
+ 'proxied': False
412
+ }
413
+ ])
414
+
415
+ return self.bulk_create_records(
416
+ zone_id=zone_id,
417
+ records=records_to_create,
418
+ validate_all=True,
419
+ stop_on_error=False
420
+ )
421
+
422
+ def _validate_record(
423
+ self,
424
+ record_type: str,
425
+ name: str,
426
+ content: str,
427
+ ttl: int,
428
+ proxied: Optional[bool],
429
+ priority: Optional[int]
430
+ ) -> Dict[str, Any]:
431
+ """Validate DNS record parameters."""
432
+ record_type = record_type.upper()
433
+
434
+ # Check if record type is supported
435
+ if record_type not in self.RECORD_TYPES:
436
+ return {
437
+ 'valid': False,
438
+ 'error': f"Unsupported record type: {record_type}"
439
+ }
440
+
441
+ record_info = self.RECORD_TYPES[record_type]
442
+
443
+ # Validate TTL
444
+ if not (300 <= ttl <= 86400):
445
+ return {
446
+ 'valid': False,
447
+ 'error': f"TTL must be between 300 and 86400 seconds"
448
+ }
449
+
450
+ # Validate proxied setting
451
+ if proxied and not record_info.get('supports_proxy', False):
452
+ return {
453
+ 'valid': False,
454
+ 'error': f"{record_type} records cannot be proxied"
455
+ }
456
+
457
+ # Validate priority for MX/SRV records
458
+ if record_info.get('requires_priority', False) and priority is None:
459
+ return {
460
+ 'valid': False,
461
+ 'error': f"{record_type} records require a priority value"
462
+ }
463
+
464
+ # Basic content validation
465
+ if not content.strip():
466
+ return {
467
+ 'valid': False,
468
+ 'error': "Record content cannot be empty"
469
+ }
470
+
471
+ return {'valid': True}
472
+
473
+ def _validate_record_config(self, config: Dict[str, Any]) -> Dict[str, Any]:
474
+ """Validate a record configuration dict."""
475
+ required_fields = ['record_type', 'name', 'content']
476
+
477
+ for field in required_fields:
478
+ if field not in config:
479
+ return {
480
+ 'valid': False,
481
+ 'error': f"Missing required field: {field}"
482
+ }
483
+
484
+ return self._validate_record(
485
+ record_type=config['record_type'],
486
+ name=config['name'],
487
+ content=config['content'],
488
+ ttl=config.get('ttl', 300),
489
+ proxied=config.get('proxied'),
490
+ priority=config.get('priority')
491
+ )
492
+
493
+ def _should_proxy_record(self, record_type: str) -> bool:
494
+ """Determine if a record type should be proxied by default."""
495
+ record_type = record_type.upper()
496
+ record_info = self.RECORD_TYPES.get(record_type, {})
497
+ return record_info.get('supports_proxy', False)