303 lines
12 KiB
Python
303 lines
12 KiB
Python
# hobo - portal to configure and deploy applications
|
|
# Copyright (C) 2015 Entr'ouvert
|
|
#
|
|
# This program is free software: you can redistribute it and/or modify it
|
|
# under the terms of the GNU Affero General Public License as published
|
|
# by the Free Software Foundation, either version 3 of the License, or
|
|
# (at your option) any later version.
|
|
#
|
|
# This program is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU Affero General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU Affero General Public License
|
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
import json
|
|
import string
|
|
import sys
|
|
import time
|
|
import os
|
|
import urlparse
|
|
|
|
from django.contrib.auth.models import User
|
|
from django.contrib.contenttypes.models import ContentType
|
|
from django.core.management.base import BaseCommand, CommandError
|
|
from django.core.management import call_command
|
|
from django.db import connection
|
|
from django.db.models import Max
|
|
from django.core.exceptions import ValidationError
|
|
from django.utils.text import slugify
|
|
|
|
from hobo.agent.common.management.commands.hobo_deploy import (
|
|
Command as HoboDeployCommand)
|
|
from hobo.multitenant.middleware import TenantMiddleware
|
|
from hobo.environment.models import (AVAILABLE_SERVICES, Authentic, Wcs, Hobo,
|
|
Passerelle, Combo, Fargo, Welco, MandayeJS, Chrono, Corbo, BiJoe,
|
|
Variable, ServiceBase)
|
|
from hobo.deploy.signals import notify_agents
|
|
from hobo.theme.utils import set_theme
|
|
from hobo.profile.models import AttributeDefinition
|
|
|
|
def get_domain(url):
|
|
return urlparse.urlparse(url).netloc.split(':')[0]
|
|
|
|
|
|
class Command(BaseCommand):
|
|
must_notify = False
|
|
verbosity = 1
|
|
|
|
def add_arguments(self, parser):
|
|
parser.add_argument('recipe', metavar='RECIPE', type=str)
|
|
parser.add_argument(
|
|
'--timeout', type=int, action='store', default=120,
|
|
help='set the timeout for the wait_operationals method')
|
|
parser.add_argument('--permissive', action='store_true', help='ignore integrity checks')
|
|
|
|
def handle(self, recipe, *args, **kwargs):
|
|
self.verbosity = kwargs.get('verbosity')
|
|
self.timeout = kwargs.get('timeout')
|
|
self.permissive = kwargs.get('permissive')
|
|
self.run_cook(recipe)
|
|
if self.verbosity:
|
|
print 'All steps executed successfully. Your environment should now be ready.'
|
|
|
|
def run_cook(self, filename):
|
|
recipe = json.load(open(filename))
|
|
variables = {}
|
|
steps = []
|
|
if 'load-variables-from' in recipe:
|
|
variables.update(json.load(open(
|
|
os.path.join(os.path.dirname(filename), recipe['load-variables-from']))))
|
|
variables.update(recipe.get('variables', {}))
|
|
for step in recipe.get('steps', []):
|
|
action, action_args = step.items()[0]
|
|
for arg in action_args:
|
|
if not isinstance(action_args[arg], basestring):
|
|
continue
|
|
action_args[arg] = string.Template(
|
|
action_args[arg]).substitute(variables)
|
|
if not self.permissive:
|
|
self.check_action(action, action_args)
|
|
steps.append((action, action_args))
|
|
|
|
for action, action_args in steps:
|
|
getattr(self, action.replace('-', '_'))(**action_args)
|
|
if self.must_notify:
|
|
notify_agents(None)
|
|
self.wait_operationals(timeout=self.timeout)
|
|
self.must_notify = False
|
|
notify_agents(None)
|
|
|
|
def wait_operationals(self, timeout):
|
|
services = []
|
|
for service_class in AVAILABLE_SERVICES:
|
|
services.extend(service_class.objects.all())
|
|
|
|
t0 = time.time()
|
|
while len(services) > 0:
|
|
for service in services[:]:
|
|
if service.last_operational_success_timestamp:
|
|
services.remove(service)
|
|
continue
|
|
service.check_operational()
|
|
if len(services) == 0:
|
|
break
|
|
if self.verbosity:
|
|
sys.stderr.write('.')
|
|
time.sleep(0.5)
|
|
if time.time() - t0 > timeout:
|
|
if self.verbosity:
|
|
sys.stderr.write('\n')
|
|
raise CommandError('timeout waiting for %s' % ', '.join(
|
|
[x.base_url for x in services]))
|
|
|
|
def create_hobo(self, url, primary=False, title=None, slug=None):
|
|
if connection.get_tenant().schema_name == 'public':
|
|
# if we're not currently in a tenant then we force the creation of
|
|
# a primary hobo
|
|
primary = True
|
|
|
|
if not primary:
|
|
if not slug:
|
|
slug = 'hobo-%s' % slugify(title)
|
|
self.create_site(Hobo, url, title, slug,
|
|
template_name='', variables=None)
|
|
domain = get_domain(url)
|
|
tenant = TenantMiddleware.get_tenant_by_hostname(domain)
|
|
connection.set_tenant(tenant)
|
|
return
|
|
|
|
domain = get_domain(url)
|
|
try:
|
|
call_command('create_hobo_tenant', domain)
|
|
except CommandError:
|
|
pass
|
|
tenant = TenantMiddleware.get_tenant_by_hostname(domain)
|
|
|
|
base_url_filename = os.path.join(tenant.get_directory(), 'base_url')
|
|
if not os.path.exists(base_url_filename):
|
|
fd = open(base_url_filename, 'w')
|
|
fd.write(url.rstrip('/'))
|
|
fd.close()
|
|
|
|
connection.set_tenant(tenant)
|
|
|
|
def create_superuser(self, username='admin', email='admin@localhost',
|
|
first_name='Admin', last_name='', password=None):
|
|
user, created = User.objects.get_or_create(
|
|
username=username, email=email, is_superuser=True)
|
|
if created:
|
|
user.first_name = first_name
|
|
user.last_name = last_name
|
|
password = password or User.objects.make_random_password(length=30)
|
|
if password:
|
|
user.set_password(password)
|
|
user.save()
|
|
if created and self.verbosity:
|
|
print 'superuser account: %s / %s' % (username, password)
|
|
|
|
def create_site(self, klass, base_url, title, slug, template_name, variables):
|
|
if slug is None:
|
|
slug = klass.Extra.service_default_slug
|
|
obj, must_save = klass.objects.get_or_create(
|
|
slug=slug,
|
|
defaults={
|
|
'title': title,
|
|
'base_url': base_url,
|
|
'template_name': template_name
|
|
})
|
|
for attr in ('title', 'base_url', 'template_name'):
|
|
if getattr(obj, attr) != locals().get(attr):
|
|
setattr(obj, attr, locals().get(attr))
|
|
must_save = True
|
|
if must_save:
|
|
try:
|
|
obj.full_clean(exclude=['last_operational_success_timestamp', 'last_operational_check_timestamp'])
|
|
except ValidationError as e:
|
|
raise CommandError(str(e))
|
|
|
|
obj.save()
|
|
self.must_notify = True
|
|
variables = variables or {}
|
|
obj_type = ContentType.objects.get_for_model(klass)
|
|
for variable_name in variables.keys():
|
|
label = variables[variable_name].get('label')
|
|
variable, created = Variable.objects.get_or_create(name=variable_name,
|
|
service_type=obj_type,
|
|
service_pk=obj.id,
|
|
defaults={'label': label or variable_name})
|
|
variable.service_type = obj_type
|
|
variable.service_pk = obj.id
|
|
if label:
|
|
variable.label = label
|
|
value = variables[variable_name].get('value')
|
|
if isinstance(value, dict) or isinstance(value, list):
|
|
value = json.dumps(value)
|
|
variable.value = value
|
|
variable.save()
|
|
|
|
def create_authentic(self, url, title, slug=None, template_name='', variables=None):
|
|
return self.create_site(Authentic, url, title, slug, template_name, variables)
|
|
|
|
def set_idp(self, url=None):
|
|
if url:
|
|
obj = Authentic.objects.get(base_url=url)
|
|
else:
|
|
obj = Authentic.objects.all()[0]
|
|
if not obj.use_as_idp_for_self:
|
|
obj.use_as_idp_for_self = True
|
|
obj.save()
|
|
|
|
def create_combo(self, url, title, slug=None, template_name='', variables=None):
|
|
return self.create_site(Combo, url, title, slug, template_name, variables)
|
|
|
|
def create_wcs(self, url, title, slug=None, template_name='', variables=None):
|
|
return self.create_site(Wcs, url, title, slug, template_name, variables)
|
|
|
|
def create_passerelle(self, url, title, slug=None, template_name='', variables=None):
|
|
return self.create_site(Passerelle, url, title, slug, template_name, variables)
|
|
|
|
def create_fargo(self, url, title, slug=None, template_name='', variables=None):
|
|
return self.create_site(Fargo, url, title, slug, template_name, variables)
|
|
|
|
def create_welco(self, url, title, slug=None, template_name='', variables=None):
|
|
return self.create_site(Welco, url, title, slug, template_name, variables)
|
|
|
|
def create_chrono(self, url, title, slug=None, template_name='', variables=None):
|
|
return self.create_site(Chrono, url, title, slug, template_name, variables)
|
|
|
|
def create_corbo(self, url, title, slug=None, template_name='', variables=None):
|
|
return self.create_site(Corbo, url, title, slug, template_name, variables)
|
|
|
|
def create_bijoe(self, url, title, slug=None, template_name='', variables=None):
|
|
return self.create_site(BiJoe, url, title, slug, template_name, variables)
|
|
|
|
def set_theme(self, theme):
|
|
set_theme(theme)
|
|
HoboDeployCommand().configure_theme(
|
|
{'variables': {'theme': theme}},
|
|
connection.get_tenant())
|
|
|
|
def set_variable(self, name, value, label=None):
|
|
variable, created = Variable.objects.get_or_create(name=name,
|
|
defaults={'label': label or name})
|
|
if isinstance(value, dict) or isinstance(value, list):
|
|
value = json.dumps(value)
|
|
if variable.label != label or variable.value != value or created:
|
|
if label:
|
|
variable.label = label
|
|
variable.value = value
|
|
variable.save()
|
|
|
|
def enable_attribute(self, name):
|
|
try:
|
|
attribute = AttributeDefinition.objects.get(name=name)
|
|
except AttributeDefinition.DoesNotExist:
|
|
return
|
|
if attribute.disabled:
|
|
attribute.disabled = False
|
|
attribute.save()
|
|
|
|
def disable_attribute(self, name):
|
|
try:
|
|
attribute = AttributeDefinition.objects.get(name=name)
|
|
except AttributeDefinition.DoesNotExist:
|
|
return
|
|
if not attribute.disabled:
|
|
attribute.disabled = True
|
|
attribute.save()
|
|
|
|
def set_attribute(self, name, label, **kwargs):
|
|
# possible keys in kwargs are: description, required,
|
|
# asked_on_registration, user_editable, user_visible, kind, order
|
|
attribute, created = AttributeDefinition.objects.get_or_create(
|
|
name=name, defaults={'label': label, 'order': 0})
|
|
kwargs['label'] = label
|
|
attribute_fields = [x.name for x in AttributeDefinition._meta.fields]
|
|
for arg in kwargs:
|
|
if arg in attribute_fields:
|
|
setattr(attribute, arg, kwargs.get(arg))
|
|
|
|
if created and not attribute.order:
|
|
attribute.order = AttributeDefinition.objects.all().aggregate(
|
|
Max('order')).get('order__max') + 1
|
|
attribute.save()
|
|
|
|
def cook(self, filename):
|
|
current_tenant = connection.get_tenant()
|
|
self.run_cook(filename)
|
|
connection.set_tenant(current_tenant)
|
|
|
|
def check_action(self, action, action_args):
|
|
if not hasattr(self, action.replace('-', '_')):
|
|
raise CommandError('Error: Unknown action %s' % action)
|
|
if 'url' in action_args.keys():
|
|
url = action_args['url']
|
|
service = ServiceBase(title='dummy', base_url=url)
|
|
if not service.is_resolvable():
|
|
raise CommandError('Error: %s is not resolvable' % url)
|
|
if not service.has_valid_certificate():
|
|
raise CommandError('Error: %s has no valid certificate' % url)
|