2015-03-23 15:36:51 +00:00
|
|
|
import re
|
2015-10-08 13:54:39 +00:00
|
|
|
import sys
|
2015-10-01 16:02:26 +00:00
|
|
|
import textwrap
|
2016-10-11 09:12:25 +00:00
|
|
|
import time
|
2016-04-06 19:00:16 +00:00
|
|
|
from functools import partial
|
2015-10-01 16:02:26 +00:00
|
|
|
from urllib.parse import urlparse
|
2015-03-23 15:36:51 +00:00
|
|
|
|
|
|
|
import requests
|
2023-10-24 16:59:02 +00:00
|
|
|
from django.utils.translation import gettext_lazy as _
|
2015-03-23 15:36:51 +00:00
|
|
|
|
2015-04-05 10:46:24 +00:00
|
|
|
from orchestra.contrib.orchestration import ServiceController
|
2015-03-23 15:36:51 +00:00
|
|
|
|
2015-09-20 12:28:22 +00:00
|
|
|
from . import ApacheTrafficByHost
|
2015-03-23 15:36:51 +00:00
|
|
|
from .. import settings
|
|
|
|
|
|
|
|
|
2016-03-08 10:16:49 +00:00
|
|
|
class WordpressMuController(ServiceController):
|
2015-04-24 11:39:20 +00:00
|
|
|
"""
|
|
|
|
Creates a wordpress site on a WordPress MultiSite installation.
|
2015-10-01 16:02:26 +00:00
|
|
|
|
|
|
|
You should point it to the database server
|
2015-04-24 11:39:20 +00:00
|
|
|
"""
|
2015-03-23 15:36:51 +00:00
|
|
|
verbose_name = _("Wordpress multisite")
|
2015-06-09 11:16:36 +00:00
|
|
|
model = 'saas.SaaS'
|
|
|
|
default_route_match = "saas.service == 'wordpress'"
|
2015-04-24 11:39:20 +00:00
|
|
|
doc_settings = (settings,
|
2016-03-31 16:02:50 +00:00
|
|
|
('SAAS_WORDPRESS_ADMIN_PASSWORD', 'SAAS_WORDPRESS_MAIN_URL', 'SAAS_WORDPRESS_VERIFY_SSL')
|
2015-04-24 11:39:20 +00:00
|
|
|
)
|
2016-03-31 16:02:50 +00:00
|
|
|
VERIFY = settings.SAAS_WORDPRESS_VERIFY_SSL
|
2015-03-23 15:36:51 +00:00
|
|
|
|
2016-10-11 09:12:25 +00:00
|
|
|
def with_retry(self, method, *args, retries=1, sleep=0.5, **kwargs):
|
|
|
|
for i in range(retries):
|
|
|
|
try:
|
2016-10-22 07:23:45 +00:00
|
|
|
return method(*args, verify=self.VERIFY, **kwargs)
|
2016-10-11 09:12:25 +00:00
|
|
|
except requests.exceptions.ConnectionError:
|
|
|
|
if i >= retries:
|
|
|
|
raise
|
2016-10-22 07:23:45 +00:00
|
|
|
sys.stderr.write("Connection error while {method}{args}, retry {i}/{retries}\n".format(
|
|
|
|
method=method.__name__, args=str(args), i=i, retries=retries))
|
2016-10-11 09:12:25 +00:00
|
|
|
time.sleep(sleep)
|
|
|
|
|
2015-03-23 15:36:51 +00:00
|
|
|
def login(self, session):
|
2015-09-29 12:35:22 +00:00
|
|
|
main_url = self.get_main_url()
|
|
|
|
login_url = main_url + '/wp-login.php'
|
2015-03-23 15:36:51 +00:00
|
|
|
login_data = {
|
|
|
|
'log': 'admin',
|
2015-04-24 11:39:20 +00:00
|
|
|
'pwd': settings.SAAS_WORDPRESS_ADMIN_PASSWORD,
|
2015-03-23 15:36:51 +00:00
|
|
|
'redirect_to': '/wp-admin/'
|
|
|
|
}
|
2016-03-31 16:02:50 +00:00
|
|
|
sys.stdout.write("Login URL: %s\n" % login_url)
|
2016-10-22 07:23:45 +00:00
|
|
|
response = self.with_retry(session.post, login_url, data=login_data)
|
2015-09-29 12:35:22 +00:00
|
|
|
if response.url != main_url + '/wp-admin/':
|
2016-03-31 16:02:50 +00:00
|
|
|
raise IOError("Failure login to remote application (%s)" % login_url)
|
2015-03-23 15:36:51 +00:00
|
|
|
|
2015-09-29 12:35:22 +00:00
|
|
|
def get_main_url(self):
|
|
|
|
main_url = settings.SAAS_WORDPRESS_MAIN_URL
|
|
|
|
return main_url.rstrip('/')
|
2015-03-23 15:36:51 +00:00
|
|
|
|
|
|
|
def validate_response(self, response):
|
|
|
|
if response.status_code != 200:
|
2016-04-06 19:00:16 +00:00
|
|
|
content = response.content.decode('utf8')
|
|
|
|
errors = re.findall(r'<body id="error-page">\n\t<p>(.*)</p></body>', content)
|
2015-03-23 15:36:51 +00:00
|
|
|
raise RuntimeError(errors[0] if errors else 'Unknown %i error' % response.status_code)
|
|
|
|
|
2015-09-10 10:34:07 +00:00
|
|
|
def get_id(self, session, saas):
|
2015-10-08 13:54:39 +00:00
|
|
|
blog_id = saas.data.get('blog_id')
|
2015-09-29 12:35:22 +00:00
|
|
|
search = self.get_main_url()
|
2015-09-10 10:34:07 +00:00
|
|
|
search += '/wp-admin/network/sites.php?s=%s&action=blogs' % saas.name
|
2015-03-23 15:36:51 +00:00
|
|
|
regex = re.compile(
|
|
|
|
'<a href="http://[\.\-\w]+/wp-admin/network/site-info\.php\?id=([0-9]+)"\s+'
|
2015-09-10 10:34:07 +00:00
|
|
|
'class="edit">%s</a>' % saas.name
|
2015-03-23 15:36:51 +00:00
|
|
|
)
|
2016-03-31 16:02:50 +00:00
|
|
|
sys.stdout.write("Search URL: %s\n" % search)
|
2016-10-22 07:23:45 +00:00
|
|
|
response = self.with_retry(session.get, search)
|
|
|
|
content = response.content.decode('utf8')
|
2015-03-23 15:36:51 +00:00
|
|
|
# Get id
|
|
|
|
ids = regex.search(content)
|
2015-10-08 13:54:39 +00:00
|
|
|
if not ids and not blog_id:
|
2015-09-10 10:34:07 +00:00
|
|
|
raise RuntimeError("Blog '%s' not found" % saas.name)
|
2015-10-08 13:54:39 +00:00
|
|
|
if ids:
|
|
|
|
ids = ids.groups()
|
|
|
|
if len(ids) > 1 and not blog_id:
|
|
|
|
raise ValueError("Multiple matches")
|
2016-04-06 19:00:16 +00:00
|
|
|
return blog_id or int(ids[0]), content
|
2015-03-23 15:36:51 +00:00
|
|
|
|
2015-09-10 10:34:07 +00:00
|
|
|
def create_blog(self, saas, server):
|
2015-10-08 13:54:39 +00:00
|
|
|
if saas.data.get('blog_id'):
|
|
|
|
return
|
|
|
|
|
2015-03-23 15:36:51 +00:00
|
|
|
session = requests.Session()
|
|
|
|
self.login(session)
|
|
|
|
|
|
|
|
# Check if blog already exists
|
|
|
|
try:
|
2016-04-06 19:00:16 +00:00
|
|
|
blog_id, content = self.get_id(session, saas)
|
2015-03-23 15:36:51 +00:00
|
|
|
except RuntimeError:
|
2015-09-29 12:35:22 +00:00
|
|
|
url = self.get_main_url()
|
2015-03-23 15:36:51 +00:00
|
|
|
url += '/wp-admin/network/site-new.php'
|
2016-03-31 16:02:50 +00:00
|
|
|
sys.stdout.write("Create URL: %s\n" % url)
|
2016-10-22 07:23:45 +00:00
|
|
|
content = self.with_retry(session.get, url).content.decode('utf8')
|
2015-03-23 15:36:51 +00:00
|
|
|
|
|
|
|
wpnonce = re.compile('name="_wpnonce_add-blog"\s+value="([^"]*)"')
|
2016-07-15 08:41:38 +00:00
|
|
|
try:
|
|
|
|
wpnonce = wpnonce.search(content).groups()[0]
|
|
|
|
except AttributeError:
|
|
|
|
raise RuntimeError("wpnonce not foud in %s" % content)
|
2015-03-23 15:36:51 +00:00
|
|
|
|
|
|
|
url += '?action=add-site'
|
|
|
|
data = {
|
2015-09-10 10:34:07 +00:00
|
|
|
'blog[domain]': saas.name,
|
|
|
|
'blog[title]': saas.name,
|
|
|
|
'blog[email]': saas.account.email,
|
2015-03-23 15:36:51 +00:00
|
|
|
'_wpnonce_add-blog': wpnonce,
|
|
|
|
}
|
|
|
|
|
|
|
|
# Validate response
|
2016-10-22 07:23:45 +00:00
|
|
|
response = self.with_retry(session.post, url, data=data)
|
2015-03-23 15:36:51 +00:00
|
|
|
self.validate_response(response)
|
2015-10-08 13:54:39 +00:00
|
|
|
blog_id = re.compile(r'<link id="wp-admin-canonical" rel="canonical" href="http(?:[^ ]+)/wp-admin/network/site-new.php\?id=([0-9]+)" />')
|
|
|
|
content = response.content.decode('utf8')
|
|
|
|
blog_id = blog_id.search(content).groups()[0]
|
|
|
|
sys.stdout.write("Created blog ID: %s\n" % blog_id)
|
|
|
|
saas.data['blog_id'] = int(blog_id)
|
|
|
|
saas.save(update_fields=('data',))
|
2016-04-06 19:00:16 +00:00
|
|
|
return True
|
2015-10-08 13:54:39 +00:00
|
|
|
else:
|
|
|
|
sys.stdout.write("Retrieved blog ID: %s\n" % blog_id)
|
|
|
|
saas.data['blog_id'] = int(blog_id)
|
|
|
|
saas.save(update_fields=('data',))
|
2015-03-23 15:36:51 +00:00
|
|
|
|
2016-04-06 19:00:16 +00:00
|
|
|
def do_action(self, action, session, id, content, saas):
|
|
|
|
url_regex = r"""<span class=["']+%s["']+><a href=["']([^>]*)['"]>""" % action
|
|
|
|
action_url = re.search(url_regex, content).groups()[0].replace("&", '&')
|
|
|
|
sys.stdout.write("%s confirm URL: %s\n" % (action, action_url))
|
|
|
|
|
2016-10-22 07:23:45 +00:00
|
|
|
content = self.with_retry(session.get, action_url).content.decode('utf8')
|
2016-04-06 19:00:16 +00:00
|
|
|
wpnonce = re.compile('name="_wpnonce"\s+value="([^"]*)"')
|
|
|
|
try:
|
|
|
|
wpnonce = wpnonce.search(content).groups()[0]
|
|
|
|
except AttributeError:
|
|
|
|
raise RuntimeError(re.search(r'<body id="error-page">([^<]+)<', content).groups()[0])
|
|
|
|
data = {
|
|
|
|
'action': action,
|
|
|
|
'id': id,
|
|
|
|
'_wpnonce': wpnonce,
|
|
|
|
'_wp_http_referer': '/wp-admin/network/sites.php',
|
|
|
|
}
|
|
|
|
action_url = self.get_main_url()
|
|
|
|
action_url += '/wp-admin/network/sites.php?action=%sblog' % action
|
|
|
|
sys.stdout.write("%s URL: %s\n" % (action, action_url))
|
2016-10-22 07:23:45 +00:00
|
|
|
response = self.with_retry(session.post, action_url, data=data)
|
2016-04-06 19:00:16 +00:00
|
|
|
self.validate_response(response)
|
|
|
|
|
|
|
|
def is_active(self, content):
|
|
|
|
return bool(
|
|
|
|
re.findall(r"""<span class=["']deactivate['"]""", content) and
|
|
|
|
not re.findall(r"""<span class=["']activate['"]""", content)
|
|
|
|
)
|
|
|
|
|
|
|
|
def activate(self, saas, server):
|
2015-03-23 15:36:51 +00:00
|
|
|
session = requests.Session()
|
|
|
|
self.login(session)
|
|
|
|
try:
|
2016-04-06 19:00:16 +00:00
|
|
|
id, content = self.get_id(session, saas)
|
2015-03-23 15:36:51 +00:00
|
|
|
except RuntimeError:
|
|
|
|
pass
|
|
|
|
else:
|
2016-04-06 19:00:16 +00:00
|
|
|
if not self.is_active(content):
|
|
|
|
return self.do_action('activate', session, id, content, saas)
|
|
|
|
|
|
|
|
def deactivate(self, saas, server):
|
|
|
|
session = requests.Session()
|
|
|
|
self.login(session)
|
|
|
|
try:
|
|
|
|
id, content = self.get_id(session, saas)
|
|
|
|
except RuntimeError:
|
|
|
|
pass
|
|
|
|
else:
|
|
|
|
if self.is_active(content):
|
|
|
|
return self.do_action('deactivate', session, id, content, saas)
|
2015-03-23 15:36:51 +00:00
|
|
|
|
2015-09-10 10:34:07 +00:00
|
|
|
def save(self, saas):
|
2016-04-06 19:00:16 +00:00
|
|
|
created = self.append(self.create_blog, saas)
|
|
|
|
if saas.active and not created:
|
|
|
|
self.append(self.activate, saas)
|
|
|
|
else:
|
|
|
|
self.append(self.deactivate, saas)
|
2015-10-01 16:02:26 +00:00
|
|
|
context = self.get_context(saas)
|
2015-10-08 13:54:39 +00:00
|
|
|
context['IDENT'] = "b.domain = '%(domain)s'" % context
|
|
|
|
if context['blog_id']:
|
|
|
|
context['IDENT'] = "b.blog_id = '%(blog_id)s'" % context
|
2015-10-01 16:02:26 +00:00
|
|
|
self.append(textwrap.dedent("""
|
|
|
|
# Update custom URL mapping
|
2015-10-08 13:54:39 +00:00
|
|
|
existing=( $(mysql -Nrs %(db_name)s --execute="
|
2015-10-01 16:02:26 +00:00
|
|
|
SELECT b.blog_id, b.domain, m.domain, b.path
|
|
|
|
FROM wp_domain_mapping AS m, wp_blogs AS b
|
2015-10-08 13:54:39 +00:00
|
|
|
WHERE m.blog_id = b.blog_id AND m.active AND %(IDENT)s;") )
|
2016-03-04 09:46:39 +00:00
|
|
|
if [[ ${existing[0]} != "" ]]; then
|
|
|
|
echo "Existing blog with ID ${existing[0]}"
|
2015-10-08 13:54:39 +00:00
|
|
|
# Clear custom domain
|
2015-10-01 16:02:26 +00:00
|
|
|
if [[ "%(custom_domain)s" == "" ]]; then
|
|
|
|
mysql %(db_name)s --execute="
|
2015-10-08 13:54:39 +00:00
|
|
|
DELETE FROM m
|
|
|
|
USING wp_domain_mapping AS m, wp_blogs AS b
|
2015-11-26 10:42:18 +00:00
|
|
|
WHERE m.blog_id = b.blog_id AND m.active AND %(IDENT)s;
|
2015-10-01 16:02:26 +00:00
|
|
|
UPDATE wp_blogs
|
|
|
|
SET path='/'
|
2015-10-29 18:19:00 +00:00
|
|
|
WHERE blog_id = ${existing[0]};"
|
2015-10-01 16:02:26 +00:00
|
|
|
elif [[ "${existing[2]}" != "%(custom_domain)s" || "${existing[3]}" != "%(custom_path)s" ]]; then
|
2015-10-08 13:54:39 +00:00
|
|
|
mysql %(db_name)s --execute="
|
2015-10-01 16:02:26 +00:00
|
|
|
UPDATE wp_domain_mapping as m, wp_blogs as b
|
2015-10-08 13:54:39 +00:00
|
|
|
SET m.domain = '%(custom_domain)s', b.path = '%(custom_path)s'
|
2015-11-26 10:42:18 +00:00
|
|
|
WHERE m.blog_id = b.blog_id AND m.active AND %(IDENT)s;"
|
2015-10-01 16:02:26 +00:00
|
|
|
fi
|
2015-10-08 13:54:39 +00:00
|
|
|
elif [[ "%(custom_domain)s" != "" ]]; then
|
2016-03-04 09:46:39 +00:00
|
|
|
echo "Non existing blog with custom domain %(domain)s"
|
2015-10-08 13:54:39 +00:00
|
|
|
blog=( $(mysql -Nrs %(db_name)s --execute="
|
2015-10-29 18:19:00 +00:00
|
|
|
SELECT blog_id, path
|
|
|
|
FROM wp_blogs
|
|
|
|
WHERE domain = '%(domain)s';") )
|
2016-03-04 09:46:39 +00:00
|
|
|
if [[ "${blog[0]}" != "" ]]; then
|
|
|
|
echo "Blog %(domain)s found, ID: ${blog[0]}"
|
2015-10-01 16:02:26 +00:00
|
|
|
mysql %(db_name)s --execute="
|
2016-03-04 09:46:39 +00:00
|
|
|
UPDATE wp_domain_mapping
|
|
|
|
SET active = 0
|
|
|
|
WHERE active AND blog_id = ${blog[0]};
|
|
|
|
INSERT INTO wp_domain_mapping
|
|
|
|
(blog_id, domain, active) VALUES (${blog[0]}, '%(custom_domain)s', 1);"
|
|
|
|
if [[ "${blog[1]}" != "%(custom_path)s" ]]; then
|
|
|
|
mysql %(db_name)s --execute="
|
|
|
|
UPDATE wp_blogs
|
|
|
|
SET path = '%(custom_path)s'
|
|
|
|
WHERE blog_id = ${blog[0]};"
|
|
|
|
fi
|
|
|
|
else
|
|
|
|
echo "Blog %(domain)s not found"
|
2015-10-01 16:02:26 +00:00
|
|
|
fi
|
|
|
|
fi""") % context
|
|
|
|
)
|
2015-03-23 15:36:51 +00:00
|
|
|
|
2016-04-06 19:00:16 +00:00
|
|
|
def delete_blog(self, saas, server):
|
|
|
|
session = requests.Session()
|
|
|
|
self.login(session)
|
|
|
|
try:
|
|
|
|
id, content = self.get_id(session, saas)
|
|
|
|
except RuntimeError:
|
|
|
|
pass
|
|
|
|
else:
|
|
|
|
return self.do_action('delete', session, id, content, saas)
|
|
|
|
|
2015-09-10 10:34:07 +00:00
|
|
|
def delete(self, saas):
|
|
|
|
self.append(self.delete_blog, saas)
|
2015-10-01 16:02:26 +00:00
|
|
|
|
|
|
|
def get_context(self, saas):
|
|
|
|
domain = saas.get_site_domain()
|
|
|
|
context = {
|
|
|
|
'db_name': settings.SAAS_WORDPRESS_DB_NAME,
|
|
|
|
'domain': domain,
|
2015-10-08 13:54:39 +00:00
|
|
|
'custom_domain': '',
|
|
|
|
'custom_path': '/',
|
|
|
|
'blog_id': saas.data.get('blog_id', ''),
|
2015-10-01 16:02:26 +00:00
|
|
|
}
|
|
|
|
if saas.custom_url:
|
|
|
|
custom_url = urlparse(saas.custom_url)
|
|
|
|
context.update({
|
|
|
|
'custom_domain': custom_url.netloc,
|
|
|
|
'custom_path': custom_url.path,
|
|
|
|
})
|
|
|
|
return context
|
2015-09-16 12:15:05 +00:00
|
|
|
|
2016-03-04 09:46:39 +00:00
|
|
|
|
2015-09-20 12:28:22 +00:00
|
|
|
class WordpressMuTraffic(ApacheTrafficByHost):
|
|
|
|
__doc__ = ApacheTrafficByHost.__doc__
|
2015-09-16 12:15:05 +00:00
|
|
|
verbose_name = _("Wordpress MU Traffic")
|
|
|
|
default_route_match = "saas.service == 'wordpress'"
|
|
|
|
doc_settings = (settings,
|
|
|
|
('SAAS_TRAFFIC_IGNORE_HOSTS', 'SAAS_WORDPRESS_LOG_PATH')
|
|
|
|
)
|
2015-09-17 11:21:35 +00:00
|
|
|
log_path = settings.SAAS_WORDPRESS_LOG_PATH
|