0001-ozwillo-keep-deployment-request-state-23885.patch
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 |
- |