From cbdd93a8628579912af4ac462b22e668893b7400 Mon Sep 17 00:00:00 2001
From: Christophe Siraut <csiraut@entrouvert.com>
Date: Mon, 3 Jun 2019 15:32:54 +0200
Subject: [PATCH] models: add Recipe (#33633)

---
 hobo/environment/management/commands/cook.py | 239 +-----------------
 hobo/environment/models.py                   | 241 +++++++++++++++++++
 2 files changed, 245 insertions(+), 235 deletions(-)

diff --git a/hobo/environment/management/commands/cook.py b/hobo/environment/management/commands/cook.py
index 0e30e28..ec8c251 100644
--- a/hobo/environment/management/commands/cook.py
+++ b/hobo/environment/management/commands/cook.py
@@ -65,239 +65,8 @@ class Command(BaseCommand):
 
     def run_cook(self, filename):
         if filename == '-':
-            recipe = json.loads(sys.stdin)
+            content = sys.stdin
         else:
-            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})
-            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)
+            content = open(filename).read()
+        recipe = Recipe(name=temp, content=content)
+        recipe.cook()
diff --git a/hobo/environment/models.py b/hobo/environment/models.py
index 8685dd5..fa1c68e 100644
--- a/hobo/environment/models.py
+++ b/hobo/environment/models.py
@@ -487,3 +487,244 @@ class BiJoe(ServiceBase):
 
 AVAILABLE_SERVICES = [Authentic, Wcs, Passerelle, Combo, Fargo, Welco,
                       MandayeJS, Chrono, Corbo, BiJoe, Hobo]
+
+
+class Recipe(models.Model):
+    name = models.CharField(max_length=100, verbose_name=_('name'))
+    content = models.TextField(blank=True, verbose_name=_('content'))
+
+    def cook(self)
+        recipe = json.loads(self.content)
+        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})
+            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)
-- 
2.20.1

