diff --git a/ROADMAP.md b/ROADMAP.md
index 86dcd0a0..e108b261 100644
--- a/ROADMAP.md
+++ b/ROADMAP.md
@@ -15,7 +15,7 @@ Note `*` _for sustancial progress_
4. [ ] Data model, crazy input validation, admin and REST interfaces, permissions, unit and functional tests, service management, migration scripts and documentation of:
1. [x] PHP/static Web applications
1. [x] Websites with Apache
- 2. [ ] *FTP/rsync/scp/shell system accounts
+ 2. [x] FTP/rsync/scp/shell system accounts
2. [ ] *Databases and database users with MySQL
1. [ ] *Mail accounts, aliases, forwards with Postfix and Dovecot
1. [x] DNS with Bind
@@ -63,4 +63,4 @@ Note `*` _for sustancial progress_
2. [ ] REST API functionality for superusers
3. [ ] Responsive user interface, based on a JS framework.
4. [ ] Full documentation
-5. [ ] [http://www.ansible.com/home](Ansible) orchestration method, which synchronize the whole service config everytime instead of incremental changes.
+5. [ ] [Ansible](http://www.ansible.com/home) orchestration method, which synchronizes the whole service config everytime instead of incremental changes.
diff --git a/orchestra/apps/domains/backends.py b/orchestra/apps/domains/backends.py
index 23f80a5e..51eb58d9 100644
--- a/orchestra/apps/domains/backends.py
+++ b/orchestra/apps/domains/backends.py
@@ -1,9 +1,11 @@
+import textwrap
+
from django.utils.translation import ugettext_lazy as _
-from . import settings
-
from orchestra.apps.orchestration import ServiceController
+from . import settings
+
class Bind9MasterDomainBackend(ServiceController):
verbose_name = _("Bind9 master domain")
@@ -52,20 +54,31 @@ class Bind9MasterDomainBackend(ServiceController):
""" reload bind if needed """
self.append('[[ $UPDATED == 1 ]] && service bind9 reload')
+ def get_servers(self, domain, backend):
+ from orchestra.apps.orchestration.models import Route, BackendOperation as Operation
+ operation = Operation(backend=backend, action='save', instance=domain)
+ servers = []
+ for server in Route.get_servers(operation):
+ servers.append(server.get_ip())
+ return servers
+
def get_context(self, domain):
context = {
'name': domain.name,
'zone_path': settings.DOMAINS_ZONE_PATH % {'name': domain.name},
'subdomains': domain.subdomains.all(),
'banner': self.get_banner(),
+ 'slaves': '; '.join(self.get_servers(domain, Bind9SlaveDomainBackend)),
}
context.update({
'conf_path': settings.DOMAINS_MASTERS_PATH,
- 'conf': 'zone "%(name)s" {\n'
- ' // %(banner)s\n'
- ' type master;\n'
- ' file "%(zone_path)s";\n'
- '};\n' % context
+ 'conf': textwrap.dedent("""
+ zone "%(name)s" {
+ // %(banner)s
+ type master;
+ file "%(zone_path)s";
+ allow-transfer { %(slaves)s; };
+ };""" % context)
})
return context
@@ -91,15 +104,19 @@ class Bind9SlaveDomainBackend(Bind9MasterDomainBackend):
def get_context(self, domain):
context = {
'name': domain.name,
- 'masters': '; '.join(settings.DOMAINS_MASTERS),
- 'subdomains': domain.subdomains.all()
+ 'banner': self.get_banner(),
+ 'subdomains': domain.subdomains.all(),
+ 'masters': '; '.join(self.get_servers(domain, Bind9MasterDomainBackend)),
}
context.update({
'conf_path': settings.DOMAINS_SLAVES_PATH,
- 'conf': 'zone "%(name)s" {\n'
- ' type slave;\n'
- ' file "%(name)s";\n'
- ' masters { %(masters)s; };\n'
- '};\n' % context
+ 'conf': textwrap.dedent("""
+ zone "%(name)s" {
+ // %(banner)s
+ type slave;
+ file "%(name)s";
+ masters { %(masters)s; };
+ allow-notify { %(masters)s; };
+ };""" % context)
})
return context
diff --git a/orchestra/apps/domains/models.py b/orchestra/apps/domains/models.py
index 63f41b6a..7cf1947c 100644
--- a/orchestra/apps/domains/models.py
+++ b/orchestra/apps/domains/models.py
@@ -124,6 +124,7 @@ class Domain(models.Model):
top = self.get_top()
if top:
self.top = top
+ self.account_id = self.account_id or top.account_id
else:
update = True
super(Domain, self).save(*args, **kwargs)
@@ -132,7 +133,7 @@ class Domain(models.Model):
for domain in domains.filter(name__endswith=self.name):
domain.top = self
domain.save(update_fields=['top'])
- self.subdomains.update(account=self.account)
+ self.subdomains.update(account_id=self.account_id)
def get_top(self):
split = self.name.split('.')
diff --git a/orchestra/apps/domains/settings.py b/orchestra/apps/domains/settings.py
index 8aa33afe..b4dcf916 100644
--- a/orchestra/apps/domains/settings.py
+++ b/orchestra/apps/domains/settings.py
@@ -4,48 +4,61 @@ from django.conf import settings
DOMAINS_DEFAULT_NAME_SERVER = getattr(settings, 'DOMAINS_DEFAULT_NAME_SERVER',
'ns.orchestra.lan')
+
DOMAINS_DEFAULT_HOSTMASTER = getattr(settings, 'DOMAINS_DEFAULT_HOSTMASTER',
'hostmaster@orchestra.lan')
+
DOMAINS_DEFAULT_TTL = getattr(settings, 'DOMAINS_DEFAULT_TTL', '1h')
+
DOMAINS_DEFAULT_REFRESH = getattr(settings, 'DOMAINS_DEFAULT_REFRESH', '1d')
+
DOMAINS_DEFAULT_RETRY = getattr(settings, 'DOMAINS_DEFAULT_RETRY', '2h')
+
DOMAINS_DEFAULT_EXPIRATION = getattr(settings, 'DOMAINS_DEFAULT_EXPIRATION', '4w')
+
DOMAINS_DEFAULT_MIN_CACHING_TIME = getattr(settings, 'DOMAINS_DEFAULT_MIN_CACHING_TIME', '1h')
+
DOMAINS_ZONE_PATH = getattr(settings, 'DOMAINS_ZONE_PATH', '/etc/bind/master/%(name)s')
+
DOMAINS_MASTERS_PATH = getattr(settings, 'DOMAINS_MASTERS_PATH', '/etc/bind/named.conf.local')
+
DOMAINS_SLAVES_PATH = getattr(settings, 'DOMAINS_SLAVES_PATH', '/etc/bind/named.conf.local')
-DOMAINS_MASTERS = getattr(settings, 'DOMAINS_MASTERS', ['10.0.3.13'])
DOMAINS_CHECKZONE_BIN_PATH = getattr(settings, 'DOMAINS_CHECKZONE_BIN_PATH',
'/usr/sbin/named-checkzone -i local')
DOMAINS_CHECKZONE_PATH = getattr(settings, 'DOMAINS_CHECKZONE_PATH', '/dev/shm')
+
DOMAINS_DEFAULT_A = getattr(settings, 'DOMAINS_DEFAULT_A', '10.0.3.13')
+
DOMAINS_DEFAULT_MX = getattr(settings, 'DOMAINS_DEFAULT_MX', (
'10 mail.orchestra.lan.',
'10 mail2.orchestra.lan.',
))
+
DOMAINS_DEFAULT_NS = getattr(settings, 'DOMAINS_DEFAULT_NS', (
'ns1.orchestra.lan.',
'ns2.orchestra.lan.',
))
+
DOMAINS_FORBIDDEN = getattr(settings, 'DOMAINS_FORBIDDEN',
# This setting prevents users from providing random domain names, i.e. google.com
# You can generate a 5K forbidden domains list from Alexa's top 1M
# wget http://s3.amazonaws.com/alexa-static/top-1m.csv.zip -O /tmp/top-1m.csv.zip
# unzip -p /tmp/top-1m.csv.zip | head -n 5000 | sed "s/^.*,//" > forbidden_domains.list
+
# '%(site_root)s/forbidden_domains.list')
'')
diff --git a/orchestra/apps/domains/tests/functional_tests/tests.py b/orchestra/apps/domains/tests/functional_tests/tests.py
index 913cb8b8..dae75954 100644
--- a/orchestra/apps/domains/tests/functional_tests/tests.py
+++ b/orchestra/apps/domains/tests/functional_tests/tests.py
@@ -26,7 +26,6 @@ class DomainTestMixin(object):
def setUp(self):
djsettings.DEBUG = True
- settings.DOMAINS_MASTERS = [self.MASTER_SERVER_ADDR]
super(DomainTestMixin, self).setUp()
self.domain_name = 'orchestra%s.lan' % random_ascii(10)
self.domain_records = (
diff --git a/orchestra/apps/domains/tests/test_domains.py b/orchestra/apps/domains/tests/test_domains.py
index 82acc3ac..f15ac2d3 100644
--- a/orchestra/apps/domains/tests/test_domains.py
+++ b/orchestra/apps/domains/tests/test_domains.py
@@ -1,17 +1,18 @@
-from django.test import TestCase
+from orchestra.utils.tests import BaseTestCase
from ..models import Domain
-class DomainTests(TestCase):
- def setUp(self):
- self.domain = Domain.objects.create(name='rostrepalid.org')
+class DomainTest(BaseTestCase):
+ def test_top_relation(self):
+ account = self.create_account()
+ domain = Domain.objects.create(name='rostrepalid.org', account=account)
Domain.objects.create(name='www.rostrepalid.org')
Domain.objects.create(name='mail.rostrepalid.org')
-
- def test_top_relation(self):
- self.assertEqual(2, len(self.domain.subdomains.all()))
+ self.assertEqual(2, len(domain.subdomains.all()))
def test_render_zone(self):
- print self.domain.render_zone()
+ account = self.create_account()
+ domain = Domain.objects.create(name='rostrepalid.org', account=account)
+ domain.render_zone()
diff --git a/orchestra/apps/orchestration/admin.py b/orchestra/apps/orchestration/admin.py
index 862f12d4..fdbbffb6 100644
--- a/orchestra/apps/orchestration/admin.py
+++ b/orchestra/apps/orchestration/admin.py
@@ -88,18 +88,16 @@ class BackendLogAdmin(admin.ModelAdmin):
)
list_display_links = ('id', 'backend')
list_filter = ('state', 'backend')
- date_hierarchy = 'updated_at'
inlines = [BackendOperationInline]
fields = [
'backend', 'server_link', 'state', 'mono_script', 'mono_stdout',
'mono_stderr', 'mono_traceback', 'exit_code', 'task_id', 'display_created',
- 'display_updated', 'execution_time'
+ 'execution_time'
]
readonly_fields = fields
server_link = admin_link('server')
- display_updated = admin_date('updated_at')
- display_created = admin_date('created_at')
+ display_created = admin_date('created_at', short_description=_("Created"))
display_state = admin_colored('state', colors=STATE_COLORS)
mono_script = display_mono('script')
mono_stdout = display_mono('stdout')
diff --git a/orchestra/apps/orchestration/helpers.py b/orchestra/apps/orchestration/helpers.py
index fb56a008..01efb6b0 100644
--- a/orchestra/apps/orchestration/helpers.py
+++ b/orchestra/apps/orchestration/helpers.py
@@ -52,9 +52,10 @@ def message_user(request, logs):
_('{errors} out of {total} banckends has fail to execute.'),
_('{errors} out of {total} banckends have fail to execute.'),
errors)
+ messages.error(request, mark_safe(msg.format(errors=errors, total=total, url=url)))
else:
msg = ungettext(
_('{total} banckend has been executed.'),
_('{total} banckends have been executed.'),
total)
- messages.warning(request, mark_safe(msg.format(errors=errors, total=total, url=url)))
+ messages.success(request, mark_safe(msg.format(total=total, url=url)))
diff --git a/orchestra/apps/orchestration/models.py b/orchestra/apps/orchestration/models.py
index bfff6ce4..cedcb492 100644
--- a/orchestra/apps/orchestration/models.py
+++ b/orchestra/apps/orchestration/models.py
@@ -1,8 +1,11 @@
+import socket
+
from django.contrib.contenttypes import generic
from django.contrib.contenttypes.models import ContentType
from django.db import models
from django.utils.translation import ugettext_lazy as _
+from orchestra.core.validators import validate_ip_address, ValidationError
from orchestra.models.fields import NullableCharField
from orchestra.utils.apps import autodiscover
@@ -27,6 +30,16 @@ class Server(models.Model):
if self.address:
return self.address
return self.name
+
+ def get_ip(self):
+ if self.address:
+ return self.address
+ try:
+ validate_ip_address(self.name)
+ except ValidationError:
+ return socket.gethostbyname(self.name)
+ else:
+ return self.name
class BackendLog(models.Model):
diff --git a/orchestra/apps/systemusers/tests/functional_tests/tests.py b/orchestra/apps/systemusers/tests/functional_tests/tests.py
index ebebf133..4cbd1b97 100644
--- a/orchestra/apps/systemusers/tests/functional_tests/tests.py
+++ b/orchestra/apps/systemusers/tests/functional_tests/tests.py
@@ -1,7 +1,6 @@
import ftplib
import os
import re
-import socket
from functools import partial
import paramiko
diff --git a/orchestra/bin/orchestra-admin b/orchestra/bin/orchestra-admin
index 8e758d88..a4cb0973 100755
--- a/orchestra/bin/orchestra-admin
+++ b/orchestra/bin/orchestra-admin
@@ -159,7 +159,8 @@ function install_requirements () {
selenium \
xvfbwrapper \
freezegun \
- coverage"
+ coverage \
+ orchestra-orm==dev"
fi
# Make sure locales are in place before installing postgres
diff --git a/orchestra/core/validators.py b/orchestra/core/validators.py
index ed1b7ca3..584387dd 100644
--- a/orchestra/core/validators.py
+++ b/orchestra/core/validators.py
@@ -29,6 +29,14 @@ def validate_ipv6_address(value):
raise ValidationError(msg)
+def validate_ip_address(value):
+ msg = _("%s is not a valid IP address") % value
+ try:
+ ip = IP(value)
+ except:
+ raise ValidationError(msg)
+
+
def validate_name(value):
"""
A single non-empty line of free-form text with no whitespace.
diff --git a/scripts/services/bind9.md b/scripts/services/bind9.md
index 723887f1..99dbacb8 100644
--- a/scripts/services/bind9.md
+++ b/scripts/services/bind9.md
@@ -12,13 +12,3 @@ Bind9 Master and Slave
mkdir /etc/bind/master
chown bind.bind /etc/bind/master
```
-
-2. Allow zone transfer on master by adding the following line to `named.conf.options`
- ```bash
- allow-transfer { slave-ip; };
- ```
-
-3. Addlow notifications on the slave server by adding the following line to `named.conf.options`
- ```bash
- allow-notify { master-ip; };
- ```
diff --git a/scripts/services/mysql.sh b/scripts/services/mysql.md
similarity index 69%
rename from scripts/services/mysql.sh
rename to scripts/services/mysql.md
index 71bfee8c..efc74d0a 100644
--- a/scripts/services/mysql.sh
+++ b/scripts/services/mysql.md
@@ -1 +1,4 @@
+MySQL
+=====
+
apt-get install mysql-server