shadowsocks-manager 0.1.1__py2-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 (88) hide show
  1. shadowsocks_manager/__init__.py +0 -0
  2. shadowsocks_manager/__main__.py +45 -0
  3. shadowsocks_manager/args_formatter/__init__.py +74 -0
  4. shadowsocks_manager/args_formatter/tests.py +42 -0
  5. shadowsocks_manager/domain/__init__.py +0 -0
  6. shadowsocks_manager/domain/admin.py +78 -0
  7. shadowsocks_manager/domain/apps.py +10 -0
  8. shadowsocks_manager/domain/migrations/0001_initial.py +79 -0
  9. shadowsocks_manager/domain/migrations/__init__.py +0 -0
  10. shadowsocks_manager/domain/models.py +265 -0
  11. shadowsocks_manager/domain/serializers.py +25 -0
  12. shadowsocks_manager/domain/tests.py +138 -0
  13. shadowsocks_manager/domain/urls.py +18 -0
  14. shadowsocks_manager/domain/views.py +38 -0
  15. shadowsocks_manager/dynamicmethod/__init__.py +0 -0
  16. shadowsocks_manager/dynamicmethod/admin.py +8 -0
  17. shadowsocks_manager/dynamicmethod/apps.py +10 -0
  18. shadowsocks_manager/dynamicmethod/migrations/__init__.py +0 -0
  19. shadowsocks_manager/dynamicmethod/models.py +96 -0
  20. shadowsocks_manager/dynamicmethod/tests.py +8 -0
  21. shadowsocks_manager/dynamicmethod/views.py +8 -0
  22. shadowsocks_manager/manage.py +29 -0
  23. shadowsocks_manager/notification/__init__.py +0 -0
  24. shadowsocks_manager/notification/admin.py +18 -0
  25. shadowsocks_manager/notification/apps.py +10 -0
  26. shadowsocks_manager/notification/migrations/0001_initial.py +40 -0
  27. shadowsocks_manager/notification/migrations/__init__.py +0 -0
  28. shadowsocks_manager/notification/models.py +66 -0
  29. shadowsocks_manager/notification/serializers.py +13 -0
  30. shadowsocks_manager/notification/tests.py +93 -0
  31. shadowsocks_manager/notification/urls.py +16 -0
  32. shadowsocks_manager/notification/views.py +19 -0
  33. shadowsocks_manager/retry/__init__.py +68 -0
  34. shadowsocks_manager/retry/tests.py +42 -0
  35. shadowsocks_manager/shadowsocks_manager/__init__.py +6 -0
  36. shadowsocks_manager/shadowsocks_manager/celery.py +27 -0
  37. shadowsocks_manager/shadowsocks_manager/settings.py +217 -0
  38. shadowsocks_manager/shadowsocks_manager/urls.py +31 -0
  39. shadowsocks_manager/shadowsocks_manager/wsgi.py +19 -0
  40. shadowsocks_manager/shadowsocksz/__init__.py +0 -0
  41. shadowsocks_manager/shadowsocksz/admin.py +188 -0
  42. shadowsocks_manager/shadowsocksz/apps.py +10 -0
  43. shadowsocks_manager/shadowsocksz/management/__init__.py +0 -0
  44. shadowsocks_manager/shadowsocksz/management/commands/__init__.py +0 -0
  45. shadowsocks_manager/shadowsocksz/management/commands/shadowsocks_config.py +24 -0
  46. shadowsocks_manager/shadowsocksz/migrations/0001_initial.py +114 -0
  47. shadowsocks_manager/shadowsocksz/migrations/__init__.py +0 -0
  48. shadowsocks_manager/shadowsocksz/models.py +1024 -0
  49. shadowsocks_manager/shadowsocksz/serializers.py +41 -0
  50. shadowsocks_manager/shadowsocksz/tasks.py +21 -0
  51. shadowsocks_manager/shadowsocksz/tests.py +470 -0
  52. shadowsocks_manager/shadowsocksz/urls.py +20 -0
  53. shadowsocks_manager/shadowsocksz/views.py +53 -0
  54. shadowsocks_manager/singleton/__init__.py +0 -0
  55. shadowsocks_manager/singleton/admin.py +8 -0
  56. shadowsocks_manager/singleton/apps.py +10 -0
  57. shadowsocks_manager/singleton/migrations/__init__.py +0 -0
  58. shadowsocks_manager/singleton/models.py +46 -0
  59. shadowsocks_manager/singleton/tests.py +8 -0
  60. shadowsocks_manager/singleton/views.py +8 -0
  61. shadowsocks_manager/statistic/__init__.py +0 -0
  62. shadowsocks_manager/statistic/admin.py +78 -0
  63. shadowsocks_manager/statistic/apps.py +10 -0
  64. shadowsocks_manager/statistic/migrations/0001_initial.py +57 -0
  65. shadowsocks_manager/statistic/migrations/0002_auto_20210313_1419.py +19 -0
  66. shadowsocks_manager/statistic/migrations/__init__.py +0 -0
  67. shadowsocks_manager/statistic/models.py +308 -0
  68. shadowsocks_manager/statistic/serializers.py +19 -0
  69. shadowsocks_manager/statistic/tasks.py +17 -0
  70. shadowsocks_manager/statistic/tests.py +99 -0
  71. shadowsocks_manager/statistic/urls.py +16 -0
  72. shadowsocks_manager/statistic/views.py +26 -0
  73. shadowsocks_manager/utils/__init__.py +0 -0
  74. shadowsocks_manager/utils/celery.py +28 -0
  75. shadowsocks_manager/utils/createsuperuser.py +57 -0
  76. shadowsocks_manager/utils/dotenv.py +70 -0
  77. shadowsocks_manager/utils/manage.py +29 -0
  78. shadowsocks_manager/utils/uwsgi.py +27 -0
  79. shadowsocks_manager-0.1.1.data/scripts/ssm-dev-start +34 -0
  80. shadowsocks_manager-0.1.1.data/scripts/ssm-dev-stop +20 -0
  81. shadowsocks_manager-0.1.1.data/scripts/ssm-setup +221 -0
  82. shadowsocks_manager-0.1.1.data/scripts/ssm-test +113 -0
  83. shadowsocks_manager-0.1.1.dist-info/LICENSE +21 -0
  84. shadowsocks_manager-0.1.1.dist-info/METADATA +477 -0
  85. shadowsocks_manager-0.1.1.dist-info/RECORD +88 -0
  86. shadowsocks_manager-0.1.1.dist-info/WHEEL +5 -0
  87. shadowsocks_manager-0.1.1.dist-info/entry_points.txt +8 -0
  88. shadowsocks_manager-0.1.1.dist-info/top_level.txt +1 -0
File without changes
@@ -0,0 +1,45 @@
1
+ #!/usr/bin/env python
2
+ import os
3
+ import sys
4
+ import subprocess
5
+ from docopt import docopt
6
+
7
+
8
+ def main():
9
+ """
10
+ Description:
11
+ Make the proxy call after adding the django root to the python path and changing the current directory to the django root.
12
+
13
+ Usage:
14
+ ssm COMMAND [OPTIONS]
15
+
16
+ Options:
17
+ COMMAND The command to be run.
18
+ OPTIONS All options are transparently passing to the called command.
19
+
20
+ Returns:
21
+ None
22
+
23
+ Example:
24
+ $ ssm python manage.py runserver
25
+ $ ssm uwsgi --ini uwsgi.ini
26
+ $ ssm celery -A shadowsocks_manager worker -l info
27
+ """
28
+ #docopt(main.__doc__)
29
+
30
+ django_root = os.path.dirname(os.path.abspath(__file__))
31
+
32
+ # add the django root to the python path to allow django commands to be run from any directory
33
+ if django_root not in sys.path:
34
+ sys.path.insert(0, django_root)
35
+
36
+ # change dir to the django root to allow the dir-sensitive commands(such as loaddata) to be run from any directory
37
+ os.chdir(django_root)
38
+
39
+
40
+ # make the proxy call
41
+ return subprocess.call(sys.argv[1:])
42
+
43
+
44
+ if __name__ == '__main__':
45
+ sys.exit(main())
@@ -0,0 +1,74 @@
1
+ # py2.7 and py3 compatibility imports
2
+ from __future__ import unicode_literals
3
+ from builtins import range
4
+ from builtins import object
5
+ from functools import reduce
6
+
7
+
8
+ class Formatter(object):
9
+ """
10
+ Return the input-syntax string presentation of args and kwargs list, delimited by comma `, `.
11
+ The value of the args and kwargs are formatted as the canonical string presentation.
12
+
13
+ Usage:
14
+ >>> f = Formatter(*args, **kwargs)
15
+ >>> print(f)
16
+
17
+ Example:
18
+ >>> print(Formatter('foo', 1, x='bar', y=2))
19
+ 'foo', 1, x='bar', y=2
20
+
21
+ Use Case:
22
+ # log the input arguments of methods.
23
+ def foo(*args, **kwargs):
24
+ message = Formatter(*args, **kwargs).to_string())
25
+ logger.debug('foo: {}'.format(message))
26
+ """
27
+
28
+ def __init__(self, *args, **kwargs):
29
+ """
30
+ Initialize the Formatter to have two members: args, kwargs
31
+ """
32
+ self.args = args
33
+ self.kwargs = kwargs
34
+
35
+ def __str__(self, *args, **kwargs):
36
+ return self.to_string(*args, **kwargs)
37
+
38
+ def args_to_string(self):
39
+ """
40
+ Return the canonical string presentation of args list, delimited by comma `, `.
41
+ """
42
+ if self.args:
43
+ self.args = [repr(arg) for arg in self.args]
44
+ formatter = ['{}' for count in range(len(self.args))]
45
+ return ', '.join(formatter).format(*self.args)
46
+
47
+
48
+ def kwargs_to_string(self):
49
+ """
50
+ Return the input-syntax string presentation of kwargs list, delimited by comma `, `.
51
+ The value of the kwargs are formatted as the canonical string presentation.
52
+ """
53
+ if self.kwargs:
54
+ formatter = ['{}={}' for count in range(len(self.kwargs))]
55
+ return ', '.join(formatter).format(
56
+ *reduce((lambda x, y: x + y), [list([k, repr(v)]) for k,v in list(self.kwargs.items())]))
57
+
58
+ def to_string(self):
59
+ """
60
+ Return the input-syntax string presentation of args and kwargs list, delimited by comma `, `.
61
+ The value of the args and kwargs are formatted as the canonical string presentation.
62
+ Be noted to the differences:
63
+ * An empty string `` is returned if no args and kwargs found.
64
+ * The input empty string `` is returned as the literal "``" with the additional quoting.
65
+ * The input NoneType None is returned as the literal `None` without additional quoting.
66
+
67
+ Example:
68
+ >>> Formatter('foo', 1, x='bar', y=2).to_string()
69
+ "'foo', 1, x='bar', y=2"
70
+ """
71
+
72
+ items = [self.args_to_string(), self.kwargs_to_string()]
73
+ items = [item for item in items if item is not None]
74
+ return ', '.join(items)
@@ -0,0 +1,42 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ # py2.7 and py3 compatibility imports
4
+ from __future__ import unicode_literals
5
+ from __future__ import absolute_import
6
+ from builtins import str
7
+
8
+ from unittest import TestCase
9
+
10
+ from . import Formatter
11
+
12
+ # Create your tests here.
13
+ class FormatterTestCase(TestCase):
14
+ def test(self):
15
+ # no args and kwargs
16
+ self.assertEqual(Formatter().to_string(), '')
17
+ # one arg as None
18
+ self.assertEqual(Formatter(None).to_string(), 'None')
19
+ # one arg as empty string
20
+ self.assertEqual(Formatter('').to_string(), repr(''))
21
+ # two simple args
22
+ self.assertEqual(Formatter('foo', 1).to_string(), '{}, 1'.format(repr('foo')))
23
+ # one arg as list
24
+ self.assertEqual(Formatter(['foo', 1]).to_string(), '[{}, 1]'.format(repr('foo')))
25
+ # one arg as dict
26
+ self.assertEqual(Formatter({'foo': 1}).to_string(), repr({'foo': 1}))
27
+ # on arg as tuple
28
+ self.assertEqual(Formatter(('foo', 1)).to_string(), repr(('foo', 1)))
29
+ # two simple kwargs, take care of the order, so works with both py2 and py3
30
+ self.assertEqual(Formatter(y=2, x='bar').to_string(), 'y=2, x={}'.format(repr('bar')))
31
+ # one kwarg as list
32
+ self.assertEqual(Formatter(x=['bar', 2]).to_string(), 'x={}'.format(repr(['bar', 2])))
33
+ # one kwarg as dict
34
+ self.assertEqual(Formatter(x={'bar': 2}).to_string(), 'x={}'.format(repr({'bar': 2})))
35
+ # one kwarg as tuple
36
+ self.assertEqual(Formatter(x=('bar', 2)).to_string(), 'x={}'.format(repr(('bar', 2))))
37
+ # two simple args and two simple kwargs, take care of the order, so works with both py2 and py3
38
+ self.assertEqual(
39
+ Formatter('foo', 1, y=2, x='bar').to_string(),
40
+ '{}, 1, y=2, x={}'.format(repr('foo'), repr('bar')))
41
+ # __str__()
42
+ self.assertEqual(str(Formatter(None)), 'None')
File without changes
@@ -0,0 +1,78 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ # py2.7 and py3 compatibility imports
4
+ from __future__ import unicode_literals
5
+
6
+ import json
7
+ from django.contrib import admin, messages
8
+
9
+ from .models import NameServer, Domain, Record
10
+
11
+
12
+ # Register your models here.
13
+
14
+ @admin.register(NameServer)
15
+ class NameServerAdmin(admin.ModelAdmin):
16
+ fields = ('name', 'api_cls_name', 'user', 'credential',
17
+ 'dt_created', 'dt_updated')
18
+
19
+ readonly_fields = ('dt_created', 'dt_updated')
20
+
21
+ list_display = ('name', 'api_cls_name', 'user', 'is_api_accessible',
22
+ 'dt_created', 'dt_updated')
23
+
24
+ def is_api_accessible(self, obj):
25
+ return obj.is_api_accessible
26
+
27
+ is_api_accessible.boolean = True
28
+ is_api_accessible.short_description = 'API'
29
+
30
+
31
+ @admin.register(Domain)
32
+ class DomainAdmin(admin.ModelAdmin):
33
+ fields = ('name', 'nameserver',
34
+ 'dt_created', 'dt_updated')
35
+
36
+ readonly_fields = ('dt_created', 'dt_updated')
37
+
38
+ list_display = ('name', 'nameserver',
39
+ 'dt_created', 'dt_updated')
40
+
41
+
42
+ @admin.register(Record)
43
+ class RecordAdmin(admin.ModelAdmin):
44
+ fields = ('host', 'domain', 'type', 'answer', 'site',
45
+ 'dt_created', 'dt_updated')
46
+
47
+ readonly_fields = ('dt_created', 'dt_updated')
48
+
49
+ list_display = ('host', 'domain', 'type', 'answer', 'answer_from_dns_api', 'is_matching_dns_api',
50
+ 'answer_from_dns_query', 'is_matching_dns_query', 'site',
51
+ 'dt_created', 'dt_updated')
52
+
53
+ def answer_from_dns_api(self, obj):
54
+ return list(obj.answer_from_dns_api) if obj.answer_from_dns_api is not None else obj.answer_from_dns_api
55
+
56
+ def answer_from_dns_query(self, obj):
57
+ return list(obj.answer_from_dns_query) if obj.answer_from_dns_query is not None else obj.answer_from_dns_query
58
+
59
+ def is_matching_dns_api(self, obj):
60
+ return obj.is_matching_dns_api
61
+
62
+ is_matching_dns_api.boolean = True
63
+ is_matching_dns_api.short_description = 'DNS API'
64
+
65
+ def is_matching_dns_query(self, obj):
66
+ return obj.is_matching_dns_query
67
+
68
+ is_matching_dns_query.boolean = True
69
+ is_matching_dns_query.short_description = 'DNS Query'
70
+
71
+ def sync_to_dns(self, request, queryset):
72
+ for obj in queryset:
73
+ result = obj.sync_to_dns()
74
+ messages.info(request, '{0}: {1}'.format(obj.host, json.dumps(result)))
75
+
76
+ sync_to_dns.short_description = 'Synchronize DNS records to DNS server for Selected Domain'
77
+
78
+ actions = (sync_to_dns,)
@@ -0,0 +1,10 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ # py2.7 and py3 compatibility imports
4
+ from __future__ import unicode_literals
5
+
6
+ from django.apps import AppConfig
7
+
8
+
9
+ class DomainConfig(AppConfig):
10
+ name = 'domain'
@@ -0,0 +1,79 @@
1
+ # -*- coding: utf-8 -*-
2
+ # Generated by Django 1.11.29 on 2024-04-21 20:23
3
+ from __future__ import unicode_literals
4
+
5
+ import django.contrib.sites.models
6
+ from django.db import migrations, models
7
+ import django.db.models.deletion
8
+
9
+
10
+ class Migration(migrations.Migration):
11
+
12
+ initial = True
13
+
14
+ dependencies = [
15
+ ('sites', '0002_alter_domain_unique'),
16
+ ]
17
+
18
+ operations = [
19
+ migrations.CreateModel(
20
+ name='Domain',
21
+ fields=[
22
+ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
23
+ ('name', models.CharField(help_text='Root domain name. Example: yourdomain.com.', max_length=64, unique=True)),
24
+ ('dt_created', models.DateTimeField(auto_now_add=True, verbose_name='Created')),
25
+ ('dt_updated', models.DateTimeField(auto_now=True, verbose_name='Updated')),
26
+ ],
27
+ ),
28
+ migrations.CreateModel(
29
+ name='NameServer',
30
+ fields=[
31
+ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
32
+ ('name', models.CharField(help_text='The name for the Nameserver, name it as your wish. Example: name.com.', max_length=64, unique=True)),
33
+ ('api_cls_name', models.CharField(choices=[('NameNsApi', 'NameNsApi')], help_text='Select the API class name for the Nameserver.', max_length=32, verbose_name='API Class')),
34
+ ('user', models.CharField(blank=True, help_text='User identity for the Nameserver API service.', max_length=64, null=True)),
35
+ ('credential', models.CharField(blank=True, help_text='User credential/token for the Nameserver API service.', max_length=128, null=True)),
36
+ ('dt_created', models.DateTimeField(auto_now_add=True, verbose_name='Created')),
37
+ ('dt_updated', models.DateTimeField(auto_now=True, verbose_name='Updated')),
38
+ ],
39
+ ),
40
+ migrations.CreateModel(
41
+ name='Record',
42
+ fields=[
43
+ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
44
+ ('host', models.CharField(help_text='Host name. Example: vpn.', max_length=64)),
45
+ ('type', models.CharField(blank=True, choices=[('A', 'A'), ('MX', 'MX'), ('CNAME', 'CNAME'), ('TXT', 'TXT'), ('SRV', 'SRV'), ('AAAA', 'AAAA'), ('NS', 'NS'), ('ANAME', 'ANAME')], max_length=8, null=True)),
46
+ ('answer', models.CharField(blank=True, help_text='Answer for the host name, comma "," is the delimiter for multiple answers.', max_length=512, null=True)),
47
+ ('dt_created', models.DateTimeField(auto_now_add=True, verbose_name='Created')),
48
+ ('dt_updated', models.DateTimeField(auto_now=True, verbose_name='Updated')),
49
+ ('domain', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='domain.Domain')),
50
+ ],
51
+ ),
52
+ migrations.CreateModel(
53
+ name='Site',
54
+ fields=[
55
+ ],
56
+ options={
57
+ 'proxy': True,
58
+ 'indexes': [],
59
+ },
60
+ bases=('sites.site',),
61
+ managers=[
62
+ ('objects', django.contrib.sites.models.SiteManager()),
63
+ ],
64
+ ),
65
+ migrations.AddField(
66
+ model_name='record',
67
+ name='site',
68
+ field=models.ForeignKey(blank=True, help_text="The record with a site will be dynamically added to Django's ALLOWED_HOSTS.", null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='records', to='domain.Site'),
69
+ ),
70
+ migrations.AddField(
71
+ model_name='domain',
72
+ name='nameserver',
73
+ field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='domain.NameServer'),
74
+ ),
75
+ migrations.AlterUniqueTogether(
76
+ name='record',
77
+ unique_together=set([('host', 'domain')]),
78
+ ),
79
+ ]
File without changes
@@ -0,0 +1,265 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ # py2.7 and py3 compatibility imports
4
+ from __future__ import unicode_literals
5
+
6
+ import logging
7
+ import socket, requests, json
8
+ from collections import defaultdict
9
+ from django.db import models
10
+ from django.db.models.signals import post_save, post_delete
11
+ from django.dispatch import receiver
12
+ from django.contrib.sites.models import Site
13
+ from django.conf import settings
14
+
15
+
16
+ logger = logging.getLogger('django')
17
+
18
+
19
+ # Create your models here.
20
+
21
+ class Site(Site):
22
+
23
+ class Meta:
24
+ proxy = True
25
+
26
+ def __str__(self):
27
+ return self.name
28
+
29
+
30
+ class BaseNsApi(object):
31
+
32
+ def __init__(self, user, credential, *args, **kwargs):
33
+ super(BaseNsApi, self).__init__(*args, **kwargs)
34
+ self.user = user
35
+ self.credential = credential
36
+
37
+ def call_api(self, url, method='get', data=None):
38
+ response = getattr(requests, method)(
39
+ url,
40
+ auth=(self.user, self.credential),
41
+ json=data
42
+ )
43
+ return response.json()
44
+
45
+ def create_record(self, *args, **kwargs):
46
+ pass
47
+
48
+ def list_records(self, *args, **kwargs):
49
+ pass
50
+
51
+ def delete_records(self, *args, **kwargs):
52
+ pass
53
+
54
+
55
+ class NameNsApi(BaseNsApi):
56
+
57
+ api_base_url = 'https://api.name.com/v4'
58
+
59
+ def create_record(self, domain, type, host, answer, ttl=300):
60
+ url = "/".join([self.api_base_url, 'domains', domain, 'records'])
61
+ data = {"host": host, "type": type, "answer": answer, "ttl": ttl}
62
+
63
+ return self.call_api(url, method='post', data=data)
64
+
65
+ def list_records(self, domain, type, host):
66
+ url = "/".join([self.api_base_url, 'domains', domain, 'records'])
67
+
68
+ records = self.call_api(url, method='get') or {}
69
+ return [item for item in records.get('records', [])
70
+ if item.get('type') == type and item.get('host') == host]
71
+
72
+ def delete_records(self, domain, type, host):
73
+ records = self.list_records(domain, type, host)
74
+
75
+ for item in records:
76
+ url = "/".join([self.api_base_url, 'domains', domain, 'records', str(item.get('id'))])
77
+ self.call_api(url, method='delete')
78
+
79
+ return records
80
+
81
+ @property
82
+ def is_accessible(self):
83
+ try:
84
+ url = "/".join([self.api_base_url, 'hello'])
85
+ return self.call_api(url, method='get').get('username') == self.user
86
+ except:
87
+ return False
88
+
89
+
90
+ class NameServer(models.Model):
91
+ API_CLASS_NAME = [
92
+ ('NameNsApi', 'NameNsApi')
93
+ ]
94
+
95
+ name = models.CharField(unique=True, max_length=64,
96
+ help_text='The name for the Nameserver, name it as your wish. Example: name.com.')
97
+ api_cls_name = models.CharField('API Class', max_length=32, choices=API_CLASS_NAME,
98
+ help_text='Select the API class name for the Nameserver.')
99
+ user = models.CharField(max_length=64, null=True, blank=True,
100
+ help_text='User identity for the Nameserver API service.')
101
+ credential = models.CharField(max_length=128, null=True, blank=True,
102
+ help_text='User credential/token for the Nameserver API service.')
103
+ dt_created = models.DateTimeField('Created', auto_now_add=True)
104
+ dt_updated = models.DateTimeField('Updated', auto_now=True)
105
+
106
+ def __str__(self):
107
+ return self.name
108
+
109
+ def __init__(self, *args, **kwargs):
110
+ super(NameServer, self).__init__(*args, **kwargs)
111
+
112
+ self.api_cls = globals().get(self.api_cls_name)
113
+
114
+ @property
115
+ def api(self):
116
+ if self.api_cls and self.user and self.credential:
117
+ return self.api_cls(self.user, self.credential)
118
+
119
+ @property
120
+ def is_api_accessible(self):
121
+ return self.api.is_accessible if self.api else None
122
+
123
+
124
+ class Domain(models.Model):
125
+ name = models.CharField(unique=True, max_length=64,
126
+ help_text='Root domain name. Example: yourdomain.com.')
127
+ nameserver = models.ForeignKey(NameServer, null=True, blank=True, on_delete=models.SET_NULL)
128
+ dt_created = models.DateTimeField('Created', auto_now_add=True)
129
+ dt_updated = models.DateTimeField('Updated', auto_now=True)
130
+
131
+ def __str__(self):
132
+ return self.name
133
+
134
+ @property
135
+ def ns_api(self):
136
+ return self.nameserver.api if self.nameserver else None
137
+
138
+
139
+ class Record(models.Model):
140
+ TYPE = [
141
+ ('A', 'A'),
142
+ ('MX', 'MX'),
143
+ ('CNAME', 'CNAME'),
144
+ ('TXT', 'TXT'),
145
+ ('SRV', 'SRV'),
146
+ ('AAAA', 'AAAA'),
147
+ ('NS', 'NS'),
148
+ ('ANAME', 'ANAME'),
149
+ ]
150
+
151
+ host = models.CharField(max_length=64,
152
+ help_text='Host name. Example: vpn.')
153
+ domain = models.ForeignKey(Domain, on_delete=models.PROTECT)
154
+ type = models.CharField(max_length=8, null=True, blank=True, choices=TYPE)
155
+ answer = models.CharField(max_length=512, null=True, blank=True,
156
+ help_text='Answer for the host name, comma "," is the delimiter for multiple answers.')
157
+ site = models.ForeignKey(Site, null=True, blank=True, on_delete=models.SET_NULL, related_name='records',
158
+ help_text="The record with a site will be dynamically added to Django's ALLOWED_HOSTS.")
159
+ dt_created = models.DateTimeField('Created', auto_now_add=True)
160
+ dt_updated = models.DateTimeField('Updated', auto_now=True)
161
+
162
+ class Meta:
163
+ unique_together = ('host', 'domain')
164
+
165
+ def __str__(self):
166
+ return self.fqdn
167
+
168
+ def save(self, *args, **kwargs):
169
+ super(Record, self).save(*args, **kwargs)
170
+ if self.site:
171
+ self.site.domain = self.fqdn
172
+ self.site.save()
173
+ settings.ALLOWED_HOSTS.update_cache()
174
+
175
+ @property
176
+ def fqdn(self):
177
+ return '.'.join([self.host, self.domain.name])
178
+
179
+ @property
180
+ def answers(self):
181
+ """
182
+ Return the record.answer in lowercase and split as Set.
183
+ """
184
+ return {
185
+ item.lower()
186
+ for item in (self.answer.split(',') if self.answer else [])
187
+ }
188
+
189
+ @property
190
+ def answer_from_dns_api(self):
191
+ """
192
+ Return the answers from DNS API, in lowercase and as Set.
193
+ """
194
+ if self.domain.ns_api:
195
+ return {
196
+ record.get('answer').lower()
197
+ for record in self.domain.ns_api.list_records(
198
+ self.domain.name, self.type, self.host
199
+ )
200
+ }
201
+
202
+ @property
203
+ def answer_from_dns_query(self):
204
+ """
205
+ Return the answers from DNS query, in lowercase and as Set.
206
+ """
207
+ ips = []
208
+ try:
209
+ truename, alias, ips = socket.gethostbyname_ex(self.fqdn)
210
+ except socket.gaierror:
211
+ # not found the host
212
+ pass
213
+ except Exception as e:
214
+ logger.error(e)
215
+ return {item.lower() for item in ips}
216
+
217
+ @property
218
+ def is_matching_dns_api(self):
219
+ """
220
+ Test if the record.answer matches the DNS API query.
221
+ """
222
+ return self.answers == self.answer_from_dns_api
223
+
224
+ @property
225
+ def is_matching_dns_query(self):
226
+ """
227
+ Test if the record.answer matches the DNS query.
228
+ """
229
+ return self.answers == self.answer_from_dns_query
230
+
231
+ def sync_to_dns(self):
232
+ """
233
+ Sync the record to DNS server through DNS API.
234
+ """
235
+ ret = defaultdict(list)
236
+ if self.domain.ns_api:
237
+ if self.is_matching_dns_api:
238
+ ret['message'] = 'No need to synchronize.'
239
+ return ret
240
+
241
+ ret['deleted'] = self.delete_from_dns()
242
+ for answer in (self.answer or '').split(','):
243
+ ret['created'].append(
244
+ self.domain.ns_api.create_record(self.domain.name, self.type, self.host, answer))
245
+ else:
246
+ ret['message'] = 'Please configure Nameserver and its User and Credential for the domain first.'
247
+
248
+ return ret
249
+
250
+ def delete_from_dns(self):
251
+ """
252
+ Delete the record from DNS server through DNS API.
253
+ """
254
+ if self.domain.ns_api:
255
+ return self.domain.ns_api.delete_records(self.domain.name, self.type, self.host)
256
+
257
+
258
+ @receiver(post_save, sender=Record)
259
+ def record_sync_to_dns(sender, instance, **kwargs):
260
+ instance.sync_to_dns()
261
+
262
+
263
+ @receiver(post_delete, sender=Record)
264
+ def record_delete_from_dns(sender, instance, **kwargs):
265
+ instance.delete_from_dns()
@@ -0,0 +1,25 @@
1
+ # py2.7 and py3 compatibility imports
2
+ from __future__ import absolute_import
3
+ from __future__ import unicode_literals
4
+
5
+ from rest_framework import serializers
6
+
7
+ from . import models
8
+
9
+
10
+ class NameServerSerializer(serializers.ModelSerializer):
11
+ class Meta:
12
+ model = models.NameServer
13
+ fields = ('id', 'name', 'api_cls_name', 'user', 'credential', 'dt_created', 'dt_updated')
14
+
15
+
16
+ class DomainSerializer(serializers.ModelSerializer):
17
+ class Meta:
18
+ model = models.Domain
19
+ fields = ('id', 'name', 'nameserver', 'dt_created', 'dt_updated')
20
+
21
+
22
+ class RecordSerializer(serializers.ModelSerializer):
23
+ class Meta:
24
+ model = models.Record
25
+ fields = ('id', 'host', 'domain', 'type', 'answer', 'site', 'dt_created', 'dt_updated')