django-cfg 1.2.14__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.
- django_cfg/__init__.py +1 -1
- django_cfg/apps/maintenance/README.md +305 -0
- django_cfg/apps/maintenance/__init__.py +27 -0
- django_cfg/apps/maintenance/admin/__init__.py +28 -0
- django_cfg/apps/maintenance/admin/deployments_admin.py +251 -0
- django_cfg/apps/maintenance/admin/events_admin.py +374 -0
- django_cfg/apps/maintenance/admin/monitoring_admin.py +215 -0
- django_cfg/apps/maintenance/admin/sites_admin.py +464 -0
- django_cfg/apps/maintenance/apps.py +105 -0
- django_cfg/apps/maintenance/management/__init__.py +0 -0
- django_cfg/apps/maintenance/management/commands/__init__.py +0 -0
- django_cfg/apps/maintenance/management/commands/maintenance.py +375 -0
- django_cfg/apps/maintenance/management/commands/sync_cloudflare.py +168 -0
- django_cfg/apps/maintenance/managers/__init__.py +20 -0
- django_cfg/apps/maintenance/managers/deployments.py +287 -0
- django_cfg/apps/maintenance/managers/events.py +374 -0
- django_cfg/apps/maintenance/managers/monitoring.py +301 -0
- django_cfg/apps/maintenance/managers/sites.py +335 -0
- django_cfg/apps/maintenance/migrations/0001_initial.py +939 -0
- django_cfg/apps/maintenance/migrations/__init__.py +0 -0
- django_cfg/apps/maintenance/models/__init__.py +27 -0
- django_cfg/apps/maintenance/models/cloudflare.py +316 -0
- django_cfg/apps/maintenance/models/maintenance.py +334 -0
- django_cfg/apps/maintenance/models/monitoring.py +393 -0
- django_cfg/apps/maintenance/models/sites.py +419 -0
- django_cfg/apps/maintenance/serializers/__init__.py +60 -0
- django_cfg/apps/maintenance/serializers/actions.py +310 -0
- django_cfg/apps/maintenance/serializers/base.py +44 -0
- django_cfg/apps/maintenance/serializers/deployments.py +209 -0
- django_cfg/apps/maintenance/serializers/events.py +210 -0
- django_cfg/apps/maintenance/serializers/monitoring.py +278 -0
- django_cfg/apps/maintenance/serializers/sites.py +213 -0
- django_cfg/apps/maintenance/services/README.md +168 -0
- django_cfg/apps/maintenance/services/__init__.py +21 -0
- django_cfg/apps/maintenance/services/cloudflare_client.py +441 -0
- django_cfg/apps/maintenance/services/dns_manager.py +497 -0
- django_cfg/apps/maintenance/services/maintenance_manager.py +504 -0
- django_cfg/apps/maintenance/services/site_sync.py +448 -0
- django_cfg/apps/maintenance/services/sync_command_service.py +330 -0
- django_cfg/apps/maintenance/services/worker_manager.py +264 -0
- django_cfg/apps/maintenance/signals.py +38 -0
- django_cfg/apps/maintenance/urls.py +36 -0
- django_cfg/apps/maintenance/views/__init__.py +18 -0
- django_cfg/apps/maintenance/views/base.py +61 -0
- django_cfg/apps/maintenance/views/deployments.py +175 -0
- django_cfg/apps/maintenance/views/events.py +204 -0
- django_cfg/apps/maintenance/views/monitoring.py +213 -0
- django_cfg/apps/maintenance/views/sites.py +338 -0
- django_cfg/apps/urls.py +5 -1
- django_cfg/core/config.py +42 -3
- django_cfg/core/generation.py +16 -5
- django_cfg/models/cloudflare.py +316 -0
- django_cfg/models/revolution.py +1 -1
- django_cfg/models/tasks.py +55 -1
- django_cfg/modules/base.py +12 -5
- django_cfg/modules/django_tasks.py +41 -3
- django_cfg/modules/django_unfold/dashboard.py +16 -1
- {django_cfg-1.2.14.dist-info → django_cfg-1.2.16.dist-info}/METADATA +2 -1
- {django_cfg-1.2.14.dist-info → django_cfg-1.2.16.dist-info}/RECORD +62 -14
- {django_cfg-1.2.14.dist-info → django_cfg-1.2.16.dist-info}/WHEEL +0 -0
- {django_cfg-1.2.14.dist-info → django_cfg-1.2.16.dist-info}/entry_points.txt +0 -0
- {django_cfg-1.2.14.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)
|