WL-1282 | Created management command for automation of theme setup.
This commit is contained in:
@@ -0,0 +1,175 @@
|
||||
"""
|
||||
This command will be run by an ansible script.
|
||||
"""
|
||||
|
||||
import os
|
||||
import json
|
||||
import fnmatch
|
||||
import logging
|
||||
|
||||
from provider.oauth2.models import Client
|
||||
from provider.constants import CONFIDENTIAL
|
||||
from edx_oauth2_provider.models import TrustedClient
|
||||
from django.contrib.auth.models import User
|
||||
from django.contrib.sites.models import Site
|
||||
from django.core.management.base import BaseCommand
|
||||
|
||||
from openedx.core.djangoapps.theming.models import SiteTheme
|
||||
from openedx.core.djangoapps.site_configuration.models import SiteConfiguration
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
"""
|
||||
Command to create the site, site themes, configuration and oauth2 clients for all WL-sites.
|
||||
|
||||
Example:
|
||||
./manage.py lms create_sites_and_configurations --dns-name whitelabel --theme-path /edx/src/edx-themes/edx-platform
|
||||
"""
|
||||
dns_name = None
|
||||
theme_path = None
|
||||
ecommerce_user = None
|
||||
discovery_user = None
|
||||
|
||||
def add_arguments(self, parser):
|
||||
"""
|
||||
Add arguments to the command parser.
|
||||
"""
|
||||
parser.add_argument(
|
||||
"--dns-name",
|
||||
type=str,
|
||||
help="Enter DNS name of sandbox.",
|
||||
required=True
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
"--theme-path",
|
||||
type=str,
|
||||
help="Enter theme directory path",
|
||||
required=True
|
||||
)
|
||||
|
||||
def _create_oauth2_client(self, url, site_name, is_discovery=True):
|
||||
"""
|
||||
Creates the oauth2 client and add it in trusted clients.
|
||||
"""
|
||||
|
||||
client, _ = Client.objects.get_or_create(
|
||||
redirect_uri="{url}complete/edx-oidc/".format(url=url),
|
||||
defaults={
|
||||
"user": self.discovery_user if is_discovery else self.ecommerce_user,
|
||||
"name": "{site_name}_{client_type}_client".format(
|
||||
site_name=site_name,
|
||||
client_type="discovery" if is_discovery else "ecommerce",
|
||||
),
|
||||
"url": url,
|
||||
"client_id": "{client_type}-key-{site_name}".format(
|
||||
client_type="discovery" if is_discovery else "ecommerce",
|
||||
site_name=site_name
|
||||
),
|
||||
"client_secret": "{client_type}-secret-{dns_name}".format(
|
||||
client_type="discovery" if is_discovery else "ecommerce",
|
||||
dns_name=self.dns_name
|
||||
),
|
||||
"client_type": CONFIDENTIAL,
|
||||
"logout_uri": "{url}logout/".format(url=url)
|
||||
}
|
||||
)
|
||||
LOG.info("Adding {client} oauth2 client as trusted client".format(client=client.name))
|
||||
TrustedClient.objects.get_or_create(client=client)
|
||||
|
||||
def _create_sites(self, site_domain, theme_dir_name, site_configuration):
|
||||
"""
|
||||
Create Sites, SiteThemes and SiteConfigurations
|
||||
"""
|
||||
site, created = Site.objects.get_or_create(
|
||||
domain=site_domain,
|
||||
defaults={"name": theme_dir_name}
|
||||
)
|
||||
if created:
|
||||
LOG.info("Creating '{site_name}' SiteTheme".format(site_name=site_domain))
|
||||
SiteTheme.objects.create(site=site, theme_dir_name=theme_dir_name)
|
||||
|
||||
LOG.info("Creating '{site_name}' SiteConfiguration".format(site_name=site_domain))
|
||||
SiteConfiguration.objects.create(site=site, values=site_configuration, enabled=True)
|
||||
else:
|
||||
LOG.info("'{site_domain}' site already exists".format(site_domain=site_domain))
|
||||
|
||||
def find(self, pattern, path):
|
||||
"""
|
||||
Matched the given pattern in given path and returns the list of matching files
|
||||
"""
|
||||
result = []
|
||||
for root, dirs, files in os.walk(path): # pylint: disable=unused-variable
|
||||
for name in files:
|
||||
if fnmatch.fnmatch(name, pattern):
|
||||
result.append(os.path.join(root, name))
|
||||
return result
|
||||
|
||||
def _get_sites_data(self):
|
||||
"""
|
||||
Reads the json files from theme directory and returns the site data in JSON format.
|
||||
"site_a":{
|
||||
"theme_dir_name": "site_a.edu.au"
|
||||
"configuration": {
|
||||
"key1": "value1",
|
||||
"key2": "value2"
|
||||
}
|
||||
}
|
||||
"""
|
||||
site_data = {}
|
||||
for config_file in self.find('sandbox_configuration.json', self.theme_path):
|
||||
LOG.info("Reading file from {file}".format(file=config_file))
|
||||
configuration_data = json.loads(
|
||||
json.dumps(
|
||||
json.load(
|
||||
open(config_file)
|
||||
)
|
||||
).replace("{dns_name}", self.dns_name)
|
||||
)['lms_configuration']
|
||||
|
||||
site_data[configuration_data['sandbox_name']] = {
|
||||
"site_domain": configuration_data['site_domain'],
|
||||
"theme_dir_name": configuration_data['theme_dir_name'],
|
||||
"configuration": configuration_data['configuration']
|
||||
}
|
||||
return site_data
|
||||
|
||||
def get_or_create_service_user(self, username):
|
||||
"""
|
||||
Creates the service user for ecommerce and discovery.
|
||||
"""
|
||||
return User.objects.get_or_create(
|
||||
username=username,
|
||||
defaults={
|
||||
"is_staff": True,
|
||||
"is_superuser": True
|
||||
}
|
||||
)
|
||||
|
||||
def handle(self, *args, **options):
|
||||
|
||||
self.theme_path = options['theme_path']
|
||||
self.dns_name = options['dns_name']
|
||||
|
||||
self.discovery_user, _ = self.get_or_create_service_user("lms_catalog_service_user")
|
||||
self.ecommerce_user, _ = self.get_or_create_service_user("ecommerce_worker")
|
||||
|
||||
all_sites = self._get_sites_data()
|
||||
|
||||
# creating Sites, SiteThemes, SiteConfigurations and oauth2 clients
|
||||
for site_name, site_data in all_sites.items():
|
||||
site_domain = site_data['site_domain']
|
||||
|
||||
discovery_url = "https://discovery-{site_domain}/".format(site_domain=site_domain)
|
||||
ecommerce_url = "https://ecommerce-{site_domain}/".format(site_domain=site_domain)
|
||||
|
||||
LOG.info("Creating '{site_name}' Site".format(site_name=site_name))
|
||||
self._create_sites(site_domain, site_data['theme_dir_name'], site_data['configuration'])
|
||||
|
||||
LOG.info("Creating discovery oauth2 client for '{site_name}' site".format(site_name=site_name))
|
||||
self._create_oauth2_client(discovery_url, site_name, is_discovery=True)
|
||||
|
||||
LOG.info("Creating ecommerce oauth2 client for '{site_name}' site".format(site_name=site_name))
|
||||
self._create_oauth2_client(ecommerce_url, site_name, is_discovery=False)
|
||||
@@ -0,0 +1,156 @@
|
||||
"""
|
||||
Test cases for create_sites_and_configurations command.
|
||||
"""
|
||||
|
||||
import mock
|
||||
|
||||
from django.test import TestCase
|
||||
from django.contrib.auth.models import User
|
||||
from django.contrib.sites.models import Site
|
||||
from django.core.management import call_command, CommandError
|
||||
|
||||
from provider.oauth2.models import Client
|
||||
from edx_oauth2_provider.models import TrustedClient
|
||||
from openedx.core.djangoapps.theming.models import SiteTheme
|
||||
|
||||
SITES = ['site_a', 'site_b']
|
||||
|
||||
|
||||
def _generate_site_config(dns_name, site_domain):
|
||||
""" Generate the site configuration for a given site """
|
||||
return {
|
||||
"lms_url": "{domain}-{dns_name}.sandbox.edx.org".format(domain=site_domain, dns_name=dns_name),
|
||||
"platform_name": "{domain}-{dns_name}".format(domain=site_domain, dns_name=dns_name)
|
||||
}
|
||||
|
||||
|
||||
def _get_sites(dns_name):
|
||||
""" Creates the mocked data for management command """
|
||||
sites = {}
|
||||
for site in SITES:
|
||||
sites.update({
|
||||
site: {
|
||||
"theme_dir_name": "{}_dir_name".format(site),
|
||||
"configuration": _generate_site_config(dns_name, site),
|
||||
"site_domain": "{site}-{dns_name}.sandbox.edx.org".format(site=site, dns_name=dns_name)
|
||||
}
|
||||
})
|
||||
return sites
|
||||
|
||||
|
||||
class TestCreateSiteAndConfiguration(TestCase):
|
||||
""" Test the create_site_and_configuration command """
|
||||
def setUp(self):
|
||||
super(TestCreateSiteAndConfiguration, self).setUp()
|
||||
|
||||
self.dns_name = "dummy_dns"
|
||||
self.theme_path = "/dummyA/dummyB/"
|
||||
|
||||
def _assert_sites_are_valid(self):
|
||||
"""
|
||||
Checks that data of all sites is valid
|
||||
"""
|
||||
sites = Site.objects.all()
|
||||
# there is an extra default site.
|
||||
self.assertEqual(len(sites), len(SITES) + 1)
|
||||
for site in sites:
|
||||
if site.name in SITES:
|
||||
site_theme = SiteTheme.objects.get(site=site)
|
||||
|
||||
self.assertEqual(
|
||||
site_theme.theme_dir_name,
|
||||
"{}_dir_name".format(site.name)
|
||||
)
|
||||
|
||||
self.assertDictEqual(
|
||||
dict(site.configuration.values),
|
||||
_generate_site_config(self.dns_name, site.name)
|
||||
)
|
||||
|
||||
def _assert_ecommerce_clients_are_valid(self):
|
||||
"""
|
||||
Checks that all ecommerce clients are valid
|
||||
"""
|
||||
service_user = User.objects.filter(username="ecommerce_worker")
|
||||
self.assertEqual(len(service_user), 1)
|
||||
self.assertTrue(service_user[0].is_staff)
|
||||
|
||||
clients = Client.objects.filter(user=service_user)
|
||||
self.assertEqual(len(clients), len(SITES))
|
||||
|
||||
for client in clients:
|
||||
self.assertEqual(client.user.username, service_user[0].username)
|
||||
site_name = client.name[:6]
|
||||
ecommerce_url = "https://ecommerce-{site_name}-{dns_name}.sandbox.edx.org/".format(
|
||||
site_name=site_name,
|
||||
dns_name=self.dns_name
|
||||
)
|
||||
self.assertEqual(client.url, ecommerce_url)
|
||||
self.assertEqual(
|
||||
client.redirect_uri,
|
||||
"{ecommerce_url}complete/edx-oidc/".format(ecommerce_url=ecommerce_url)
|
||||
)
|
||||
self.assertEqual(
|
||||
len(TrustedClient.objects.filter(client=client)),
|
||||
1
|
||||
)
|
||||
|
||||
def _assert_discovery_clients_are_valid(self):
|
||||
"""
|
||||
Checks that all discovery clients are valid
|
||||
"""
|
||||
service_user = User.objects.filter(username="lms_catalog_service_user")
|
||||
self.assertEqual(len(service_user), 1)
|
||||
self.assertTrue(service_user[0].is_staff)
|
||||
|
||||
clients = Client.objects.filter(user=service_user)
|
||||
self.assertEqual(len(clients), len(SITES))
|
||||
|
||||
for client in clients:
|
||||
self.assertEqual(client.user.username, service_user[0].username)
|
||||
site_name = client.name[:6]
|
||||
discovery_url = "https://discovery-{site_name}-{dns_name}.sandbox.edx.org/".format(
|
||||
site_name=site_name,
|
||||
dns_name=self.dns_name
|
||||
)
|
||||
self.assertEqual(client.url, discovery_url)
|
||||
self.assertEqual(
|
||||
client.redirect_uri,
|
||||
"{discovery_url}complete/edx-oidc/".format(discovery_url=discovery_url)
|
||||
)
|
||||
self.assertEqual(
|
||||
len(TrustedClient.objects.filter(client=client)),
|
||||
1
|
||||
)
|
||||
|
||||
def test_without_dns(self):
|
||||
""" Test the command without dns_name """
|
||||
with self.assertRaises(CommandError):
|
||||
call_command(
|
||||
"create_sites_and_configurations"
|
||||
)
|
||||
|
||||
@mock.patch(
|
||||
'openedx.core.djangoapps.theming.management.commands.create_sites_and_configurations.Command._get_sites_data'
|
||||
)
|
||||
def test_with_dns(self, mock_get_sites):
|
||||
""" Test the command with dns_name """
|
||||
mock_get_sites.return_value = _get_sites(self.dns_name)
|
||||
call_command(
|
||||
"create_sites_and_configurations",
|
||||
"--dns-name", self.dns_name,
|
||||
"--theme-path", self.theme_path
|
||||
)
|
||||
self._assert_sites_are_valid()
|
||||
self._assert_discovery_clients_are_valid()
|
||||
self._assert_ecommerce_clients_are_valid()
|
||||
|
||||
call_command(
|
||||
"create_sites_and_configurations",
|
||||
"--dns-name", self.dns_name,
|
||||
"--theme-path", self.theme_path
|
||||
)
|
||||
# if we run command with same dns then it will not duplicates the sites and oauth2 clients.
|
||||
self._assert_sites_are_valid()
|
||||
self._assert_discovery_clients_are_valid()
|
||||
self._assert_ecommerce_clients_are_valid()
|
||||
Reference in New Issue
Block a user