Projet

Général

Profil

0001-ozwillo-keep-deployment-request-state-23885.patch

Benjamin Dauvergne, 17 mai 2018 14:23

Télécharger (29,9 ko)

Voir les différences:

Subject: [PATCH] ozwillo: keep deployment request state (#23885)

Fields added to OzwilloInstance:
* state
* deploy_data
* created
* modified

First migration initialize all instances with the state DEPLOYED but new
instance will get the state NEW (change done in second migration).

OzwilloInstance was registered in the admin for managing deployments.
 hobo/contrib/ozwillo/admin.py                 |  10 +
 hobo/contrib/ozwillo/management/__init__.py   |   0
 .../ozwillo/management/commands/__init__.py   |   0
 .../management/commands/ozwillo_worker.py     |  68 +++++
 .../migrations/0002_auto_20180517_1047.py     |  52 ++++
 hobo/contrib/ozwillo/models.py                | 256 +++++++++++++++++-
 hobo/contrib/ozwillo/views.py                 | 218 ++-------------
 .../management/commands/runscript.py          |   2 +-
 8 files changed, 414 insertions(+), 192 deletions(-)
 create mode 100644 hobo/contrib/ozwillo/admin.py
 create mode 100644 hobo/contrib/ozwillo/management/__init__.py
 create mode 100644 hobo/contrib/ozwillo/management/commands/__init__.py
 create mode 100644 hobo/contrib/ozwillo/management/commands/ozwillo_worker.py
 create mode 100644 hobo/contrib/ozwillo/migrations/0002_auto_20180517_1047.py
hobo/contrib/ozwillo/admin.py
1
from django.contrib import admin
2

  
3
from .models import OzwilloInstance
4

  
5

  
6
class OzwilloInstanceAdmin(admin.ModelAdmin):
7
    list_display = ['id', 'domain_slug', 'external_ozwillo_id', 'state', 'created', 'modified']
8
    list_filter = ['state', 'created', 'modified']
9

  
10
admin.site.register(OzwilloInstance, OzwilloInstanceAdmin)
hobo/contrib/ozwillo/management/commands/ozwillo_worker.py
1
# Ozwillo plugin to deploy Publik
2
# Copyright (C) 2017  Entr'ouvert
3
#
4
# This program is free software: you can redistribute it and/or modify it
5
# under the terms of the GNU Affero General Public License as published
6
# by the Free Software Foundation, either version 3 of the License, or
7
# (at your option) any later version.
8
#
9
# This program is distributed in the hope that it will be useful,
10
# but WITHOUT ANY WARRANTY; without even the implied warranty of
11
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
# GNU Affero General Public License for more details.
13
#
14
# You should have received a copy of the GNU Affero General Public License
15
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
16

  
17
import logging
18

  
19
from hobo.contrib.ozwillo.models import OzwilloInstance
20

  
21
from django.db.transaction import atomic
22
from django.core.management.base import BaseCommand
23

  
24

  
25
class Command(BaseCommand):
26
    def add_arguments(self, parser):
27
        parser.add_argument('args', nargs='*')
28

  
29
    @atomic
30
    def handle(self, *args, **options):
31
        qs = OzwilloInstance.objects.select_for_update()
32

  
33
        # deployment
34
        if args:
35
            # allow deploying manually failed instances
36
            to_deploy = qs.fiter(
37
                domain_slug__in=args,
38
                state__in=[OzwilloInstance.STATE_TO_DEPLOY, OzwilloInstance.STATE_DEPLOY_ERROR])
39
        else:
40
            # deploy everything to be deployed
41
            to_deploy = qs.filter(
42
                state=OzwilloInstance.STATE_TO_DEPLOY)
43
        for instance in to_deploy:
44
            try:
45
                with atomic():
46
                    instance.deploy()
47
            except Exception:
48
                logger.exception(u'ozwillo: deploying %s from CRON failed', instance)
49
                instance.deploy_error()
50

  
51
        # destruction
52
        if args:
53
            # allow deploying manually failed instances
54
            to_destroy = qs.filter(
55
                domain_slug__in=args,
56
                state__in=[OzwilloInstance.STATE_TO_DESTROY, OzwilloInstance.STATE_DESTROY_ERROR])
57
        else:
58
            # destroy everything to be destroyed
59
            to_destroy = qs.filter(state=OzwilloInstance.STATE_TO_DESTROY)
60
        for instance in to_destroy:
61
            try:
62
                with atomic():
63
                    instance.destroy()
64
            except Exception:
65
                logger.exception(u'ozwillo: destroying %s from CRON failed', instance)
66
                instance.destroy_error()
67

  
68
logger = logging.getLogger(__name__)
hobo/contrib/ozwillo/migrations/0002_auto_20180517_1047.py
1
# -*- coding: utf-8 -*-
2
# Generated by Django 1.11.13 on 2018-05-17 10:47
3
from __future__ import unicode_literals
4

  
5
from django.db import migrations, models
6
import django.utils.timezone
7

  
8

  
9
class Migration(migrations.Migration):
10

  
11
    dependencies = [
12
        ('ozwillo', '0001_initial'),
13
    ]
14

  
15
    operations = [
16
        migrations.AddField(
17
            model_name='ozwilloinstance',
18
            name='created',
19
            field=models.DateTimeField(auto_now_add=True, default=django.utils.timezone.now),
20
            preserve_default=False,
21
        ),
22
        migrations.AddField(
23
            model_name='ozwilloinstance',
24
            name='deploy_data',
25
            field=models.TextField(null=True),
26
        ),
27
        migrations.AddField(
28
            model_name='ozwilloinstance',
29
            name='modified',
30
            field=models.DateTimeField(auto_now=True, default=django.utils.timezone.now),
31
        ),
32
        migrations.AddField(
33
            model_name='ozwilloinstance',
34
            name='state',
35
            field=models.CharField(choices=[(b'new', b'new'), (b'to_deploy', b'to_deploy'), (b'deployed', b'deployed'), (b'deploy_error', b'deploy_error'), (b'to_destroy', b'to_destroy'), (b'destroy_error', b'destroy_error'), (b'destroyed', b'destroyed')], default=b'deployed', max_length=16),
36
        ),
37
        migrations.AlterField(
38
            model_name='ozwilloinstance',
39
            name='external_ozwillo_id',
40
            field=models.CharField(max_length=450, unique=True),
41
        ),
42
        migrations.AlterField(
43
            model_name='ozwilloinstance',
44
            name='modified',
45
            field=models.DateTimeField(auto_now=True),
46
        ),
47
        migrations.AlterField(
48
            model_name='ozwilloinstance',
49
            name='state',
50
            field=models.CharField(choices=[(b'new', b'new'), (b'to_deploy', b'to_deploy'), (b'deployed', b'deployed'), (b'deploy_error', b'deploy_error'), (b'to_destroy', b'to_destroy'), (b'destroy_error', b'destroy_error'), (b'destroyed', b'destroyed')], default=b'new', max_length=16),
51
        ),
52
    ]
hobo/contrib/ozwillo/models.py
14 14
# You should have received a copy of the GNU Affero General Public License
15 15
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
16 16

  
17
import logging
18
import os
19
import subprocess
20
import tempfile
21
import json
22

  
23
import requests
24

  
25
from django.conf import settings
17 26
from django.db import models
27
from django.db.transaction import atomic
28
from django.utils.text import slugify
29
from django.core.management import call_command
18 30

  
19 31

  
20 32
class OzwilloInstance(models.Model):
33
    STATE_NEW = 'new'
34
    STATE_TO_DEPLOY = 'to_deploy'
35
    STATE_DEPLOY_ERROR = 'deploy_error'
36
    STATE_DEPLOYED = 'deployed'
37
    STATE_TO_DESTROY = 'to_destroy'
38
    STATE_DESTROY_ERROR = 'destroy_error'
39
    STATE_DESTROYED = 'destroyed'
40

  
41
    STATES = [
42
        (STATE_NEW, 'new'),
43
        (STATE_TO_DEPLOY, 'to_deploy'),
44
        (STATE_DEPLOYED, 'deployed'),
45
        (STATE_DEPLOY_ERROR, 'deploy_error'),
46
        (STATE_TO_DESTROY, 'to_destroy'),
47
        (STATE_DESTROY_ERROR, 'destroy_error'),
48
        (STATE_DESTROYED, 'destroyed'),
49
    ]
50

  
51
    state = models.CharField(max_length=16, default=STATE_NEW, choices=STATES)
52
    created = models.DateTimeField(auto_now_add=True)
53
    modified = models.DateTimeField(auto_now=True)
21 54
    domain_slug = models.CharField(max_length=250, unique=True)
22
    external_ozwillo_id = models.CharField(max_length=450)
55
    external_ozwillo_id = models.CharField(max_length=450, unique=True)
56
    deploy_data = models.TextField(null=True)
23 57

  
24 58
    def __unicode__(self):
25
        return 'external ozwillo id: %s, domain slug: %s' % (self.external_ozwillo_id,
26
                                                             self.domain_slug)
59
        return self.domain_slug
60

  
61
    def __repr__(self):
62
        return '<OzwilloInstance external_ozwillo_id: %r domain_slug: %r state: %r>' % (
63
            self.external_ozwillo_id, self.domain_slug, self.state)
64

  
65
    @property
66
    def data(self):
67
        return json.loads(self.deploy_data) if self.deploy_data else None
68

  
69
    def to_deploy(self):
70
        assert self.state == self.STATE_NEW
71
        try:
72
            with atomic():
73
                # lock the new instance to prevent collision with the CRON job
74
                OzwilloInstance.objects.select_for_update().filter(id=self.id)
75
                # instance starts with the NEW state, to prevent the CRON from
76
                # deploying instance juste created
77
                # only instance in the state STATE_TO_DEPLOY are deployed.
78
                self.state = self.STATE_TO_DEPLOY
79
                self.save()
80
                self.deploy()
81
        except Exception:
82
            # something failed, still make the instance to be deployed
83
            # an reraise exception
84
            self.state = self.STATE_TO_DEPLOY
85
            self.save()
86
            raise
87

  
88
    def to_destroy(self):
89
        assert self.state == self.STATE_DEPLOYED
90
        self.state = self.STATE_TO_DESTROY
91
        self.save()
92

  
93
    def deploy_error(self):
94
        assert self.state in [self.STATE_DEPLOY_ERROR, self.STATE_TO_DEPLOY]
95
        if self.state == self.STATE_TO_DEPLOY:
96
            self.state = self.STATE_DEPLOY_ERROR
97
            self.save()
98

  
99
    def deploy(self):
100
        logger.info(u'ozwillo: deploy start for %s', self)
101
        data = self.data
102
        if not data:
103
            logger.warning(u'ozwillo: unable to deploy, no data')
104
            return
105

  
106
        # Request parsing
107
        client_id = data['client_id']
108
        client_secret = data['client_secret']
109
        instance_id = data['instance_id']
110
        instance_name = data['organization_name']
111
        instance_name = slugify(instance_name)
112
        registration_uri = data['instance_registration_uri']
113
        user = data['user']
114

  
115
        # Cook new platform
116
        template_recipe = json.load(open('/etc/hobo/ozwillo/template_recipe.json', 'rb'))
117
        var = template_recipe['variables']
118
        for key, value in var.items():
119
            var[key] = value.replace('instance_name', instance_name)
120

  
121
        template_recipe['variables'] = var
122
        domain = var['combo']
123
        domain_agent = var['combo_agent']
124
        domain_passerelle = var['passerelle']
125
        logger.info(u'ozwillo: cooking %s', template_recipe)
126

  
127
        with tempfile.NamedTemporaryFile() as recipe_file:
128
            json.dump(template_recipe, recipe_file)
129
            recipe_file.flush()
130
            call_command('cook', recipe_file.name, timeout=1000, verbosity=0)
131

  
132
        # Load user portal template
133
        logger.info(u'ozwillo: loading combo template')
134
        run_command([
135
            'sudo', '-u', 'combo',
136
            'combo-manage', 'tenant_command', 'import_site',
137
            '/etc/hobo/ozwillo/import-site-template.json',
138
            '-d', domain
139
        ])
140

  
141
        # Load agent portal template
142
        logger.info(u'ozwillo: loading combo agent template')
143
        run_command([
144
            'sudo', '-u', 'combo',
145
            'combo-manage', 'tenant_command', 'import_site',
146
            '/etc/hobo/ozwillo/import-site-agents.json',
147
            '-d', domain_agent
148
        ])
149

  
150
        # Configure OIDC Ozwillo authentication
151
        logger.info(u'ozwillo: configuring OIDC ozwillo authentication')
152
        domain_name = 'connexion-%s.%s' % (instance_name, settings.OZWILLO_ENV_DOMAIN)
153
        if run_command([
154
                'sudo', '-u', 'authentic-multitenant',
155
                'authentic2-multitenant-manage', 'tenant_command', 'oidc-register-issuer',
156
                '-d', domain_name,
157
                '--scope', 'profile',
158
                '--scope', 'email',
159
                '--issuer', settings.OZWILLO_PLATEFORM,
160
                '--client-id', client_id,
161
                '--client-secret', client_secret,
162
                '--claim-mapping', 'given_name first_name always_verified',
163
                '--claim-mapping', 'family_name last_name always_verified',
164
                '--ou-slug', 'default',
165
                '--claim-mapping', 'email email required',
166
                'Ozwillo'
167
        ]):
168
            # creation of the admin user depends upon the creation of provider
169
            logger.info(u'ozwillo: creating admin user')
170
            create_user_script = os.path.dirname(__file__) + '/scripts/create_user_ozwillo.py'
171
            run_command([
172
                'sudo', '-u', 'authentic-multitenant',
173
                'authentic2-multitenant-manage', 'tenant_command', 'runscript', '-d', domain_name,
174
                create_user_script, user['email_address'], user['id'], user['name']
175
            ])
176

  
177
        # Load passerelle template
178
        logger.info(u'ozwillo: loading passerelle template')
179
        run_command([
180
            'sudo', '-u', 'passerelle',
181
            'passerelle-manage', 'tenant_command', 'import_site',
182
            '/etc/hobo/ozwillo/import-site-passerelle.json',
183
            '--import-user', '-d', domain_passerelle
184
        ])
185

  
186
        # Sending done event to Ozwillo
187
        services = {
188
            'services': [{
189
                'local_id': 'publik',
190
                'name': 'Publik - %s' % (instance_name),
191
                'service_uri': 'https://connexion-%s.%s/accounts/oidc/login?iss=%s'
192
                               % (instance_name, settings.OZWILLO_ENV_DOMAIN,
193
                                  settings.OZWILLO_PLATEFORM),
194
                'description': 'Gestion de la relation usagers',
195
                'tos_uri': 'https://publik.entrouvert.com/',
196
                'policy_uri': 'https://publik.entrouvert.com/',
197
                'icon': 'https://publik.entrouvert.com/static/img/logo-publik-64x64.png',
198
                'payment_option': 'FREE',
199
                'target_audience': ['PUBLIC_BODIES',
200
                                    'CITIZENS',
201
                                    'COMPANIES'],
202
                'contacts': ['https://publik.entrouvert.com/'],
203
                'redirect_uris': ['https://connexion-%s.%s/accounts/oidc/callback/'
204
                                  % (instance_name, settings.OZWILLO_ENV_DOMAIN)],
205
            }],
206
            'instance_id': instance_id,
207
            'destruction_uri': settings.OZWILLO_DESTRUCTION_URI,
208
            'destruction_secret': settings.OZWILLO_DESTRUCTION_SECRET,
209
            'needed_scopes': []
210
        }
211
        logger.info(u'ozwillo: sending registration request, %r', services)
212
        headers = {'Content-type': 'application/json', 'Accept': 'application/json'}
213
        response = requests.post(
214
            registration_uri,
215
            data=json.dumps(services),
216
            auth=(client_id, client_secret),
217
            headers=headers)
218
        logger.info(u'ozwillo: registration response, status=%s content=%r',
219
                    response.status_code, response.content)
220
        self.state = self.STATE_DEPLOYED
221
        self.save()
222
        logger.info(u'ozwillo: deploy finished')
223

  
224
    def deploy_error(self):
225
        assert self.state in [self.STATE_DESTROY_ERROR, self.STATE_TO_DESTROY]
226
        if self.state == self.STATE_TO_DESTROY:
227
            self.state = self.STATE_DESTROY_ERROR
228
            self.save()
229

  
230
    def destroy(self):
231
        logger.info(u'ozwillo: delete start for %s', self)
232
        instance_slug = self.domain_slug
233
        services = settings.OZWILLO_SERVICES
234
        wcs = services['wcs-au-quotidien']
235

  
236
        for s, infos in services.items():
237
            # to get the two combo instances which have same service
238
            service_user = s.split('_')[0]
239

  
240
            tenant = '%s%s.%s' % (infos[0], instance_slug, settings.OZWILLO_ENV_DOMAIN)
241

  
242
            run_command([
243
                'sudo', '-u', service_user, infos[1],
244
                'delete_tenant', '--force-drop', tenant
245
            ])
246

  
247
        tenant = '%s%s.%s' % (wcs[0], instance_slug, settings.OZWILLO_ENV_DOMAIN)
248
        run_command([
249
            'sudo', '-u', 'wcs',
250
            wcs[1], '-f', '/etc/wcs/wcs-au-quotidien.cfg',
251
            'delete_tenant', '--force-drop', tenant
252
        ])
253

  
254
        self.delete()
255
        logger.info(u'ozwillo: destroy thread finished')
256

  
257

  
258
def run_command(args):
259
    try:
260
        process = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
261
    except OSError as e:
262
        logger.error('ozwillo: launching subprocess %s raised error %s', args, e)
263
        return False
264
    logger.info('ozwillo: launching subprocess with pid %s : %s', process.pid, args)
265
    stdoutdata, stderrdata = process.communicate()
266
    if process.returncode != 0:
267
        logger.error('ozwillo: subprocess %s failed returncode=%s stdout=%r stderr=%r',
268
                     process.pid, process.returncode, stdoutdata, stderrdata)
269
        return False
270
    logger.info('ozwillo: subprocess terminated')
271
    return True
272

  
273

  
274
logger = logging.getLogger(__name__)
275

  
276

  
hobo/contrib/ozwillo/views.py
14 14
# You should have received a copy of the GNU Affero General Public License
15 15
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
16 16

  
17
import os
18 17
import logging
19
import requests
20 18
import json
21
import subprocess
22 19
import hmac
23 20
import threading
24
import tempfile
25 21
from hashlib import sha1
26 22

  
27 23
from django.views.decorators.csrf import csrf_exempt
28 24
from django.conf import settings
29 25
from django.http import (HttpResponseForbidden, HttpResponseBadRequest,
30 26
                         HttpResponseNotFound, HttpResponse)
31
from django.core.management import call_command
32 27
from django.utils.text import slugify
28
from django.db import DatabaseError
29
from django.db.transaction import atomic
33 30

  
34 31
from .models import OzwilloInstance
35 32

  
36 33
logger = logging.getLogger(__name__)
37 34

  
38 35

  
39
def run_command(args):
40
    try:
41
        process = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
42
    except OSError as e:
43
        logger.error('ozwillo: launching subprocess %s raised error %s', args, e)
44
        return False
45
    logger.debug('ozwillo: launching subprocess with pid %s : %s', process.pid, args)
46
    stdoutdata, stderrdata = process.communicate()
47
    if process.returncode != 0:
48
        logger.error('ozwillo: subprocess %s failed returncode=%s stdout=%r stderr=%r',
49
                     process.pid, process.returncode, stdoutdata, stderrdata)
50
        return False
51
    logger.debug('ozwillo: subprocess terminated')
52
    return True
53

  
54

  
55 36
def valid_signature_required(setting):
56 37
    '''Validate Ozwillo signatures'''
57 38
    signature_header_name = 'HTTP_X_HUB_SIGNATURE'
......
97 78
        logger.warning(u'ozwillo: received non JSON request')
98 79
        return HttpResponseBadRequest('invalid JSON content')
99 80

  
100
    logger.debug(u'ozwillo: create publik instance request, %r', data)
81
    logger.info(u'ozwillo: create publik instance request, %r', data)
101 82

  
102 83
    if 'organization_name' not in data.keys():
103 84
        logger.warning(u'ozwillo: missing organization_name')
......
108 89
        logger.warning(u'ozwillo: instance %s already exists', org_name)
109 90
        return HttpResponseBadRequest('instance %s already exists' % org_name)
110 91

  
111
    OzwilloInstance.objects.create(external_ozwillo_id=data['instance_id'], domain_slug=org_name)
112

  
92
    try:
93
        instance = OzwilloInstance.objects.create(
94
            external_ozwillo_id=data['instance_id'],
95
            state=OzwilloInstance.NEW,
96
            domain_slug=org_name,
97
            # deploy_data is a TextField containing JSON
98
            deploy_data=json.dumps(data, indent=4))
99
    except DatabaseError as e:
100
        logger.warning(u'ozwillo: could not create instance_id %r org_name %r: %r',
101
                       data['instance_id'], org_name, e)
102
        return HttpResponseBadRequest(u'cannot create the instance: %r', e)
103

  
104
    # immediate deploy in a thread
113 105
    def thread_function(data):
114 106
        try:
115
            ozwillo_deploy_thread(data)
107
            instance.to_deploy()
116 108
        except Exception:
117
            logger.exception(u'ozwillo: error occured while deploying instance %s',
118
                             data['organization_name'])
119

  
109
            logger.exception(u'ozwillo: error occured duging initial deploy request %s', org_name)
120 110
    thread = threading.Thread(target=thread_function, args=(data,))
121 111
    thread.start()
122 112

  
123 113
    return HttpResponse()
124 114

  
125 115

  
126
def ozwillo_deploy_thread(data):
127
    logger.debug(u'ozwillo: deploy thread start')
128
    # Request parsing
129
    client_id = data['client_id']
130
    client_secret = data['client_secret']
131
    instance_id = data['instance_id']
132
    instance_name = data['organization_name']
133
    instance_name = slugify(instance_name)
134
    registration_uri = data['instance_registration_uri']
135
    user = data['user']
136

  
137
    # Cook new platform
138
    template_recipe = json.load(open('/etc/hobo/ozwillo/template_recipe.json', 'rb'))
139
    var = template_recipe['variables']
140
    for key, value in var.items():
141
        var[key] = value.replace('instance_name', instance_name)
142

  
143
    template_recipe['variables'] = var
144
    domain = var['combo']
145
    domain_agent = var['combo_agent']
146
    domain_passerelle = var['passerelle']
147
    logger.debug(u'ozwillo: cooking %s', template_recipe)
148

  
149
    with tempfile.NamedTemporaryFile() as recipe_file:
150
        json.dump(template_recipe, recipe_file)
151
        recipe_file.flush()
152
        call_command('cook', recipe_file.name, timeout=1000, verbosity=0)
153

  
154
    # Load user portal template
155
    logger.debug(u'ozwillo: loading combo template')
156
    run_command([
157
        'sudo', '-u', 'combo',
158
        'combo-manage', 'tenant_command', 'import_site',
159
        '/etc/hobo/ozwillo/import-site-template.json',
160
        '-d', domain
161
    ])
162

  
163
    # Load agent portal template
164
    logger.debug(u'ozwillo: loading combo agent template')
165
    run_command([
166
        'sudo', '-u', 'combo',
167
        'combo-manage', 'tenant_command', 'import_site',
168
        '/etc/hobo/ozwillo/import-site-agents.json',
169
        '-d', domain_agent
170
    ])
171

  
172
    # Configure OIDC Ozwillo authentication
173
    logger.debug(u'ozwillo: configuring OIDC ozwillo authentication')
174
    domain_name = 'connexion-%s.%s' % (instance_name, settings.OZWILLO_ENV_DOMAIN)
175
    if run_command([
176
            'sudo', '-u', 'authentic-multitenant',
177
            'authentic2-multitenant-manage', 'tenant_command', 'oidc-register-issuer',
178
            '-d', domain_name,
179
            '--scope', 'profile',
180
            '--scope', 'email',
181
            '--issuer', settings.OZWILLO_PLATEFORM,
182
            '--client-id', client_id,
183
            '--client-secret', client_secret,
184
            '--claim-mapping', 'given_name first_name always_verified',
185
            '--claim-mapping', 'family_name last_name always_verified',
186
            '--ou-slug', 'default',
187
            '--claim-mapping', 'email email required',
188
            'Ozwillo'
189
    ]):
190
        # creation of the admin user depends upon the creation of provider
191
        logger.debug(u'ozwillo: creating admin user')
192
        create_user_script = os.path.dirname(__file__) + '/scripts/create_user_ozwillo.py'
193
        run_command([
194
            'sudo', '-u', 'authentic-multitenant',
195
            'authentic2-multitenant-manage', 'tenant_command', 'runscript', '-d', domain_name,
196
            create_user_script, user['email_address'], user['id'], user['name']
197
        ])
198

  
199
    # Load passerelle template
200
    logger.debug(u'ozwillo: loading passerelle template')
201
    run_command([
202
        'sudo', '-u', 'passerelle',
203
        'passerelle-manage', 'tenant_command', 'import_site',
204
        '/etc/hobo/ozwillo/import-site-passerelle.json',
205
        '--import-user', '-d', domain_passerelle
206
    ])
207

  
208
    # Sending done event to Ozwillo
209
    services = {
210
        'services': [{
211
            'local_id': 'publik',
212
            'name': 'Publik - %s' % (instance_name),
213
            'service_uri': 'https://connexion-%s.%s/accounts/oidc/login?iss=%s'
214
                           % (instance_name, settings.OZWILLO_ENV_DOMAIN,
215
                              settings.OZWILLO_PLATEFORM),
216
            'description': 'Gestion de la relation usagers',
217
            'tos_uri': 'https://publik.entrouvert.com/',
218
            'policy_uri': 'https://publik.entrouvert.com/',
219
            'icon': 'https://publik.entrouvert.com/static/img/logo-publik-64x64.png',
220
            'payment_option': 'FREE',
221
            'target_audience': ['PUBLIC_BODIES',
222
                                'CITIZENS',
223
                                'COMPANIES'],
224
            'contacts': ['https://publik.entrouvert.com/'],
225
            'redirect_uris': ['https://connexion-%s.%s/accounts/oidc/callback/'
226
                              % (instance_name, settings.OZWILLO_ENV_DOMAIN)],
227
        }],
228
        'instance_id': instance_id,
229
        'destruction_uri': settings.OZWILLO_DESTRUCTION_URI,
230
        'destruction_secret': settings.OZWILLO_DESTRUCTION_SECRET,
231
        'needed_scopes': []
232
    }
233
    logger.debug(u'ozwillo: sending registration request, %r', services)
234
    headers = {'Content-type': 'application/json', 'Accept': 'application/json'}
235
    response = requests.post(
236
        registration_uri,
237
        data=json.dumps(services),
238
        auth=(client_id, client_secret),
239
        headers=headers)
240
    logger.debug(u'ozwillo: registration response, status=%s content=%r', response.status_code,
241
                 response.content)
242
    logger.debug(u'ozwillo: deploy thread finished')
243

  
244

  
245 116
@csrf_exempt
246 117
@is_ozwillo_enabled
247 118
@valid_signature_required(setting='OZWILLO_DESTRUCTION_SECRET')
119
@atomic
248 120
def delete_publik_instance(request):
249 121
    try:
250 122
        data = json.loads(request.body)
......
252 124
        logger.warning(u'ozwillo: received non JSON request')
253 125
        return HttpResponseBadRequest('invalid JSON content')
254 126

  
255
    logger.debug(u'ozwillo: delete publik instance request, %r', data)
127
    logger.info(u'ozwillo: delete publik instance request (%r)', data)
256 128

  
257 129
    try:
258
        instance = OzwilloInstance.objects.get(external_ozwillo_id=data['instance_id'])
130
        instance_id = data['instance_id']
131
    except Exception:
132
        logger.warning(u'ozwillo: no instance id in destroy request (%r)', data)
133
        return HttpResponseBadRequest('no instance id')
134
    try:
135
        instance = OzwilloInstance.objects.select_for_update().get(
136
            external_ozwillo_id=instance_id,
137
            # only deployed instances can be destroyed
138
            state=OzwilloInstance.STATE_DEPLOYED)
259 139
    except OzwilloInstance.DoesNotExist:
260 140
        return HttpResponseBadRequest('no instance with id %s' % data['instance_id'])
261

  
262
    def thread_function(data):
263
        try:
264
            ozwillo_destroy_thread(instance)
265
            logger.debug(u'ozwillo: instance %s destroyed', instance.domain_slug)
266
        except Exception:
267
            logger.exception('ozwillo: error occured while destroying instance %s',
268
                             instance.domain_slug)
269
    thread = threading.Thread(target=ozwillo_destroy_thread, args=(instance,))
270
    thread.start()
271

  
141
    instance.to_destroy()
272 142
    return HttpResponse(status=200)
273

  
274

  
275
def ozwillo_destroy_thread(instance):
276
    logger.debug(u'ozwillo: destroy thread start')
277
    instance_slug = instance.domain_slug
278
    services = settings.OZWILLO_SERVICES
279
    wcs = services['wcs-au-quotidien']
280

  
281
    for s, infos in services.items():
282
        # to get the two combo instances which have same service
283
        service_user = s.split('_')[0]
284

  
285
        tenant = '%s%s.%s' % (infos[0], instance_slug, settings.OZWILLO_ENV_DOMAIN)
286

  
287
        run_command([
288
            'sudo', '-u', service_user, infos[1],
289
            'delete_tenant', '--force-drop', tenant
290
        ])
291

  
292
    tenant = '%s%s.%s' % (wcs[0], instance_slug, settings.OZWILLO_ENV_DOMAIN)
293
    run_command([
294
        'sudo', '-u', 'wcs',
295
        wcs[1], '-f', '/etc/wcs/wcs-au-quotidien.cfg',
296
        'delete_tenant', '--force-drop', tenant
297
    ])
298

  
299
    instance.delete()
300
    logger.debug(u'ozwillo: destroy thread finished')
hobo/multitenant/management/commands/runscript.py
24 24
class Command(BaseCommand):
25 25

  
26 26
    def add_arguments(self, parser):
27
        parser.add_argument('args', nargs=argparse.REMAINDER)
27
        parser.add_argument('args', nargs='*')
28 28

  
29 29
    def handle(self, *args, **options):
30 30
        fullpath = os.path.dirname(os.path.abspath(args[0]))
31
-