Projet

Général

Profil

0002-add-new-agent-task-to-provision-objects-to-tenants-f.patch

Benjamin Dauvergne, 15 septembre 2015 09:54

Télécharger (14,7 ko)

Voir les différences:

Subject: [PATCH 2/3] add new agent task to provision objects to tenants (fixes
 #8217)

First use is to connect it to post_save, post_delete signal on Role
model of authentic, to propagate roles to tenants.
 debian/agent/sudo-hobo-agent                       |  5 ++
 debian/debian_config_common.py                     |  4 +
 hobo/agent/authentic2/apps.py                      | 73 +++++++++++++++++
 .../authentic2/locale/fr/LC_MESSAGES/django.po     | 35 ++++++++
 hobo/agent/authentic2/role_forms.py                | 95 ++++++++++++++++++++++
 hobo/agent/common/__init__.py                      | 25 ++++++
 .../common/management/commands/hobo_notify.py      |  6 ++
 hobo/agent/worker/celery.py                        |  8 ++
 hobo/agent/worker/services.py                      | 33 +++++++-
 9 files changed, 280 insertions(+), 4 deletions(-)
 create mode 100644 hobo/agent/authentic2/locale/fr/LC_MESSAGES/django.po
 create mode 100644 hobo/agent/authentic2/role_forms.py
 create mode 100644 hobo/agent/common/management/commands/hobo_notify.py
debian/agent/sudo-hobo-agent
3 3
hobo-agent ALL=(combo)NOPASSWD:/usr/bin/combo-manage hobo_deploy * -
4 4
hobo-agent ALL=(passerelle)NOPASSWD:/usr/bin/passerelle-manage hobo_deploy * -
5 5
hobo-agent ALL=(fargo)NOPASSWD:/usr/bin/fargo-manage hobo_deploy * -
6
hobo-agent ALL=(wcs-au-quotidien)NOPASSWD:/usr/sbin/wcsctl -f /etc/wcs/wcs-au-quotidien.cfg hobo_notify -
7
hobo-agent ALL=(authentic-multitenant)NOPASSWD:/usr/bin/authentic2-multitenant-manage hobo_notify -
8
hobo-agent ALL=(combo)NOPASSWD:/usr/bin/combo-manage hobo_notify -
9
hobo-agent ALL=(passerelle)NOPASSWD:/usr/bin/passerelle-manage hobo_notify -
10
hobo-agent ALL=(fargo)NOPASSWD:/usr/bin/fargo-manage hobo_notify -
debian/debian_config_common.py
176 176
LANGUAGES = (('fr', u'Fran\xe7ais'),)
177 177
USE_L10N = True
178 178
USE_TZ = True
179

  
180
# Celery configuration
181
BROKER_URL = 'amqp://'
182
BROKER_TASK_EXPIRES = 120
hobo/agent/authentic2/apps.py
1
import json
2

  
1 3
from django.apps import AppConfig
4
from django.db.models.signals import post_save, post_delete
5
from django.db.models import Q
6
from django.conf import settings
7

  
8
from django_rbac.utils import get_role_model
9

  
10
from hobo.agent.common import notify_agents
11
from authentic2.utils import to_list
12
from authentic2.saml.models import LibertyProvider
13

  
14

  
15
def get_ou(role_or_through):
16
    if hasattr(role_or_through, 'ou'):
17
        return role_or_through.ou
18
    else:
19
        return role_or_through.role.ou
20

  
21

  
22
def get_audience(role_or_through):
23
    ou = get_ou(role_or_through)
24
    if ou:
25
        qs = LibertyProvider.objects.filter(ou=ou)
26
    else:
27
        qs = LibertyProvider.objects.filter(ou__isnull=True)
28
    return list(qs.values_list('entity_id', flat=True))
29

  
30

  
31
def get_related_roles(role_or_through):
32
    ou = get_ou(role_or_through)
33
    Role = get_role_model()
34
    qs = Role.objects.filter(admin_scope_id__isnull=True) \
35
        .prefetch_related('attributes')
36
    if ou:
37
        qs = qs.filter(ou=ou)
38
    else:
39
        qs = qs.filter(ou__isnull=True)
40
    for role in qs:
41
        role.emails = []
42
        role.emails_to_members = False
43
        for attribute in role.attributes.all():
44
            if attribute.name in ('emails', 'emails_to_members') and attribute.kind == 'json':
45
                setattr(role, attribute.name, json.loads(attribute.value))
46
    return qs
47

  
48

  
49
def notify_roles(sender, instance, **kwargs):
50
    notify_agents({
51
        '@type': 'provision',
52
        'audience': get_audience(instance),
53
        'full': True,
54
        'objects': [
55
            {
56
                '@type': 'role',
57
                'uuid': role.uuid,
58
                'name': role.name,
59
                'slug': role.slug,
60
                'description': role.description,
61
                'emails': role.emails,
62
                'emails_to_members': role.emails_to_members,
63
            } for role in get_related_roles(instance)
64
        ]
65
    })
66

  
2 67

  
3 68
class Authentic2AgentConfig(AppConfig):
4 69
    name = 'hobo.agent.authentic2'
5 70
    label = 'authentic2_agent'
6 71
    verbose_name = 'Authentic2 Agent'
72

  
73
    def ready(self):
74
        Role = get_role_model()
75
        post_save.connect(notify_roles, Role)
76
        post_delete.connect(notify_roles, Role)
77
        post_save.connect(notify_roles, Role.members.through)
78
        post_delete.connect(notify_roles, Role.members.through)
79
        settings.A2_MANAGER_ROLE_FORM_CLASS = 'hobo.agent.authentic2.role_forms.RoleForm'
hobo/agent/authentic2/locale/fr/LC_MESSAGES/django.po
1
# Translation of hobo
2
# Copyright (C) 2015 Entr'ouvert
3
# This file is distributed under the same license as the hobo package.
4
# Benjamin Dauvergne <bdauvergne@entrouvert.com>, 2015
5
#
6
msgid ""
7
msgstr ""
8
"Project-Id-Version: hobo 0\n"
9
"Report-Msgid-Bugs-To: \n"
10
"POT-Creation-Date: 2015-06-11 15:06+0200\n"
11
"PO-Revision-Date: 2014-03-24 19:31+0100\n"
12
"Last-Translator: Benjamin Dauvergne <bdauvergne@entrouvert.com>\n"
13
"Language: French\n"
14
"MIME-Version: 1.0\n"
15
"Content-Type: text/plain; charset=UTF-8\n"
16
"Content-Transfer-Encoding: 8bit\n"
17
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
18

  
19
#: management/commands/hobo_deploy.py:148
20
#: management/commands/import-wcs-roles.py:49
21
msgid "Superuser"
22
msgstr "Super-utilisateur"
23

  
24
#: role_forms.py:23
25
#, python-brace-format
26
msgid "Item {0} is invalid: {1}"
27
msgstr "L'élément {0} est invalide: {1}"
28

  
29
#: role_forms.py:62
30
msgid "Emails"
31
msgstr "Courriels"
32

  
33
#: role_forms.py:65
34
msgid "Emails to members"
35
msgstr "Propager les courriels à tous les utilisateurs ayant ce rôle"
hobo/agent/authentic2/role_forms.py
1
import json
2

  
3
from django import forms
4
from django.core import validators
5
from django.core.exceptions import ValidationError
6
from django.utils.translation import ugettext_lazy as _
7

  
8
from authentic2.a2_rbac.models import RoleAttribute, Role
9
from authentic2.validators import EmailValidator
10
from authentic2.manager.forms import RoleEditForm
11

  
12

  
13
class ListValidator(object):
14
    def __init__(self, item_validator):
15
        self.item_validator = item_validator
16

  
17
    def __call__(self, value):
18
        for i, item in enumerate(value):
19
            try:
20
                self.item_validator(item)
21
            except ValidationError, e:
22
                raise ValidationError(
23
                    _('Item {0} is invalid: {1}') % (i, e.args[0]))
24

  
25

  
26
class CommaSeparatedInput(forms.TextInput):
27
    def _format_value(self, value):
28
        return u', '.join(value)
29

  
30

  
31
class CommaSeparatedCharField(forms.Field):
32
    widget = CommaSeparatedInput
33

  
34
    def __init__(self, dedup=True, max_length=None, min_length=None, *args,
35
                 **kwargs):
36
        self.dedup = dedup
37
        self.max_length = max_length
38
        self.min_length = min_length
39
        item_validators = kwargs.pop('item_validators', [])
40
        super(CommaSeparatedCharField, self).__init__(*args, **kwargs)
41
        for item_validator in item_validators:
42
            self.validators.append(ListValidator(item_validator))
43

  
44
    def to_python(self, value):
45
        if value in validators.EMPTY_VALUES:
46
            return []
47

  
48
        value = [item.strip() for item in value.split(',') if item.strip()]
49
        if self.dedup:
50
            value = list(set(value))
51

  
52
        return value
53

  
54
    def clean(self, value):
55
        value = self.to_python(value)
56
        self.validate(value)
57
        self.run_validators(value)
58
        return value
59

  
60

  
61
class RoleForm(RoleEditForm):
62
    emails = CommaSeparatedCharField(label=_('Emails'),
63
                                           item_validators=[EmailValidator()])
64
    emails_to_members = forms.BooleanField(required=False,
65
                                           label=_('Emails to members'))
66

  
67
    def __init__(self, *args, **kwargs):
68
        instance = kwargs.get('instance')
69
        if instance:
70
            fields = Role._meta.get_all_field_names()
71
            initial = kwargs.setdefault('initial', {})
72
            role_attributes = RoleAttribute.objects.filter(role=instance,
73
                                                           kind='json')
74
            for role_attribute in role_attributes:
75
                if role_attribute.name in fields:
76
                    continue
77
                initial[role_attribute.name] = json.loads(role_attribute.value)
78
        super(RoleForm, self).__init__(*args, **kwargs)
79

  
80
    def save(self, commit=True):
81
        fields = Role._meta.get_all_field_names()
82
        assert commit
83
        instance = super(RoleForm, self).save(commit=commit)
84
        for field in self.cleaned_data:
85
            if field in fields:
86
                continue
87
            value = json.dumps(self.cleaned_data[field])
88
            ra, created = RoleAttribute.objects.get_or_create(
89
                role=instance, name=field, kind='json',
90
                defaults={'value': value})
91
            if not created and ra.value != value:
92
                ra.value = value
93
                ra.save()
94
        instance.save()
95
        return instance
hobo/agent/common/__init__.py
1
from celery import Celery
2
from kombu.common import Broadcast
3

  
4
from django.conf import settings
5
from django.db import connection
6

  
7

  
8
def notify_agents(data):
9
    '''Send notifications to all other tenants'''
10
    notification = {
11
        'tenant': connection.get_tenant().domain_url,
12
        'data': data,
13
    }
14
    with Celery('hobo', broker=settings.BROKER_URL) as app:
15
        app.conf.update(
16
            CELERY_TASK_SERIALIZER='json',
17
            CELERY_ACCEPT_CONTENT=['json'],
18
            CELERY_RESULT_SERIALIZER='json',
19
            CELERY_QUEUES=(Broadcast('broadcast_tasks'), )
20
        )
21
        # see called method in hobo.agent.worker.celery
22
        app.send_task('hobo-notify',
23
                      (notification,),
24
                      expires=settings.BROKER_TASK_EXPIRES,
25
                      queue='broadcast_tasks')
hobo/agent/common/management/commands/hobo_notify.py
1
from django.core.management.base import BaseCommand
2

  
3

  
4
class Command(BaseCommand):
5
    def handle(self, *args, **kwargs):
6
        pass
hobo/agent/worker/celery.py
13 13
    CELERY_QUEUES=(Broadcast('broadcast_tasks'), )
14 14
)
15 15

  
16

  
16 17
@app.task(name='hobo-deploy', bind=True)
17 18
def deploy(self, environment):
18 19
    services.deploy(environment)
20

  
21

  
22
@app.task(name='hobo-notify', bind=True, acks_late=True)
23
def hobo_notify(self, notification):
24
    assert 'tenant' in notification
25
    assert 'data' in notification
26
    services.notify(notification['data'])
hobo/agent/worker/services.py
1
import sys
1 2
import ConfigParser
2 3
import fnmatch
3 4
import json
......
19 20
        self.title = title
20 21
        self.secret_key = secret_key
21 22

  
22
    def is_for_us(self):
23
    @classmethod
24
    def is_for_us(cls, url):
23 25
        # This function checks if the requested service is to be hosted
24 26
        # on this server, and return True if appropriate.
25 27
        #
......
30 32
        # (ex: "! *.dev.au-quotidien.com").
31 33
        if not settings.AGENT_HOST_PATTERNS:
32 34
            return True
33
        patterns = settings.AGENT_HOST_PATTERNS.get(self.service_id)
35
        patterns = settings.AGENT_HOST_PATTERNS.get(cls.service_id)
34 36
        if patterns is None:
35 37
            return True
36
        parsed_url = urllib2.urlparse.urlsplit(self.base_url)
38
        parsed_url = urllib2.urlparse.urlsplit(url)
37 39
        netloc = parsed_url.netloc
38 40
        match = False
39 41
        for pattern in patterns:
......
55 57
                shell=True, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
56 58
        stdout = cmd_process.communicate(input=json.dumps(environment))
57 59

  
60
    @classmethod
61
    def notify(cls, data):
62
        for audience in data.get('audience', []):
63
            if cls.is_for_us(audience):
64
                break
65
        else:
66
            return
67
        cmd = cls.service_manage_cmd + ' hobo_notify -'
68
        try:
69
            cmd_process = subprocess.Popen(cmd, shell=True, stdin=subprocess.PIPE,
70
                                           stdout=subprocess.PIPE, stderr=subprocess.PIPE)
71
        except OSError:
72
            return
73
        stdout, stderr = cmd_process.communicate(input=json.dumps(data))
74
        if cmd_process.returncode != 0:
75
            raise RuntimeError('command "%s" failed: %r %r' % (cmd, stdout, stderr))
76

  
58 77

  
59 78
class Passerelle(BaseService):
60 79
    service_id = 'passerelle'
......
106 125
        if not service_id in service_classes:
107 126
            continue
108 127
        service_obj = service_classes.get(service_id)(**service)
109
        if not service_obj.is_for_us():
128
        if not service_obj.is_for_us(service_obj.base_url):
110 129
            logger.debug('skipping as not for us: %r', service_obj)
111 130
            continue
112 131
        if service_obj.check_timestamp(hobo_timestamp):
113 132
            logger.debug('skipping uptodate site: %r', service_obj)
114 133
            continue
115 134
        service_obj.execute(environment)
135

  
136
def notify(data):
137
    for klassname, service in globals().items():
138
        if not hasattr(service, 'service_id'):
139
            continue
140
        service.notify(data)
116
-