models.py 17.6 KB
Newer Older
Lukas Burgey's avatar
Lukas Burgey committed
1
2
3
4
# django senders need their arguments
# pylint: disable=unused-argument

import json
5
import time
Lukas Burgey's avatar
Lukas Burgey committed
6
import logging
7
import pika
8
from pika.exceptions import ConnectionClosed
Lukas Burgey's avatar
Lukas Burgey committed
9
10
import requests
from requests.auth import HTTPBasicAuth
Lukas Burgey's avatar
Lukas Burgey committed
11
from django.conf import settings
Lukas Burgey's avatar
Lukas Burgey committed
12
from django.contrib.auth.models import AbstractUser, Group
13
from django.core.cache import cache
Lukas Burgey's avatar
Lukas Burgey committed
14
from django.db import models
15
from django.db.models.signals import post_save, pre_delete
Lukas Burgey's avatar
Lukas Burgey committed
16
from django.dispatch import receiver
17
from django_mysql.models import JSONField
Lukas Burgey's avatar
Lukas Burgey committed
18
from rest_framework.authtoken.models import Token
Lukas Burgey's avatar
Lukas Burgey committed
19
from .auth.v1.models import OIDCConfig
Lukas Burgey's avatar
Lukas Burgey committed
20

Lukas Burgey's avatar
Lukas Burgey committed
21
LOGGER = logging.getLogger(__name__)
22
23
RECONNECT_TIMEOUT = 5
RECONNECT_RETRIES = 3
24

Lukas Burgey's avatar
Lukas Burgey committed
25

26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
# singleton for simple configs
# https://steelkiwi.com/blog/practical-application-singleton-design-pattern/
class SingletonModel(models.Model):
    class Meta:
        abstract = True

    def set_cache(self):
        cache.set(self.__class__.__name__, self)

    # pylint: disable=invalid-name, arguments-differ
    def save(self, *args, **kwargs):
        self.pk = 1
        super(SingletonModel, self).save(*args, **kwargs)
        self.set_cache()

    @classmethod
    def load(cls):
        if cache.get(cls.__name__) is None:
            obj, created = cls.objects.get_or_create(pk=1)
            if not created:
                obj.set_cache()
        return cache.get(cls.__name__)


Lukas Burgey's avatar
Lukas Burgey committed
50
51
# clients are registerred at rabbitmq, when they are assigned to a site
# (because we only then know what services they provide)
52
class RabbitMQInstance(SingletonModel):
Lukas Burgey's avatar
Lukas Burgey committed
53
    host = models.CharField(
Lukas Burgey's avatar
Lukas Burgey committed
54
55
56
        max_length=150,
        default='localhost',
    )
Lukas Burgey's avatar
Lukas Burgey committed
57
58
59
60
    vhost = models.CharField(
        max_length=150,
        default='%2f',
    )
Lukas Burgey's avatar
Lukas Burgey committed
61
    exchange = models.CharField(
Lukas Burgey's avatar
Lukas Burgey committed
62
63
64
        max_length=150,
        default='deployments',
    )
Lukas Burgey's avatar
Lukas Burgey committed
65
    port = models.IntegerField(
Lukas Burgey's avatar
Lukas Burgey committed
66
        default=15672,
Lukas Burgey's avatar
Lukas Burgey committed
67
    )
Lukas Burgey's avatar
Lukas Burgey committed
68
    path = models.CharField(
Lukas Burgey's avatar
Lukas Burgey committed
69
70
71
        max_length=150,
        default='api',
    )
Lukas Burgey's avatar
Lukas Burgey committed
72
    username = models.CharField(
Lukas Burgey's avatar
Lukas Burgey committed
73
74
75
        max_length=150,
        default='guest',
    )
Lukas Burgey's avatar
Lukas Burgey committed
76
    password = models.CharField(
Lukas Burgey's avatar
Lukas Burgey committed
77
78
79
        max_length=150,
        default='guest',
    )
Lukas Burgey's avatar
Lukas Burgey committed
80
81
    is_active = models.BooleanField(
        default=True,
Lukas Burgey's avatar
Lukas Burgey committed
82
    )
Lukas Burgey's avatar
Lukas Burgey committed
83
84
85
86

    def __str__(self):
        return self.host

Lukas Burgey's avatar
Lukas Burgey committed
87
    def msg(self, msg):
Lukas Burgey's avatar
Lukas Burgey committed
88
        return '[RabbitMQ:{}] {}'.format(self.host, msg)
Lukas Burgey's avatar
Lukas Burgey committed
89
90
91
92

    @property
    def auth(self):
        return HTTPBasicAuth(
Lukas Burgey's avatar
Lukas Burgey committed
93
            self.username,
Lukas Burgey's avatar
Lukas Burgey committed
94
            self.password,
Lukas Burgey's avatar
Lukas Burgey committed
95
        )
Lukas Burgey's avatar
Lukas Burgey committed
96
97

    @property
Lukas Burgey's avatar
Lukas Burgey committed
98
99
100
101
102
    def _connection_parameters(self):
        return pika.ConnectionParameters(
            host=self.host,
            ssl=True,
        )
103

Lukas Burgey's avatar
Lukas Burgey committed
104
105
    # PUBLIC API

106
    def publish_by_service(self, service, msg):
107
108
109
        # FIXME dirty
        tries = 0
        while tries < RECONNECT_RETRIES:
110
            try:
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
                # open connection
                connection = pika.BlockingConnection(
                    self._connection_parameters,
                )

                # open channel
                channel = connection.channel()
                channel.exchange_declare(
                    exchange=self.exchange,
                    durable=True,
                    auto_delete=False,
                    exchange_type='topic',
                )
                channel.confirm_delivery()

                channel.basic_publish(
127
128
129
130
131
132
133
                    exchange=self.exchange,
                    routing_key=service.routing_key,
                    body=msg,
                    properties=pika.BasicProperties(
                        delivery_mode=1,
                    ),
                )
134
135
136
137
                channel.close()
                connection.close()
                return
            except:
138
                time.sleep(RECONNECT_TIMEOUT)
Lukas Burgey's avatar
Lukas Burgey committed
139

140
141
            tries += 1

Lukas Burgey's avatar
Lukas Burgey committed
142

143
144
145
146
def user_info_default():
    return {}


Lukas Burgey's avatar
Lukas Burgey committed
147
class User(AbstractUser):
148
    TYPE_CHOICES = (
Lukas Burgey's avatar
Lukas Burgey committed
149
150
151
152
        ('apiclient', 'API-Client'),
        ('oidcuser', 'OIDC User'),
        ('admin', 'Admin'),
    )
153
    user_type = models.CharField(
Lukas Burgey's avatar
Lukas Burgey committed
154
155
156
157
        max_length=20,
        choices=TYPE_CHOICES,
        default='oidcuser',
    )
158
159
160
161
    sub = models.CharField(
        max_length=150,
        blank=True,
        null=True,
162
        editable=False,
163
164
165
166
167
168
169
170
171
172
173
174
    )
    password = models.CharField(
        max_length=150,
        blank=True,
        null=True,
    )
    # the real state of the user
    # (self.is_active is the supposed state of the user)
    _is_active = models.BooleanField(
        default=True,
        editable=False,
    )
Lukas Burgey's avatar
Lukas Burgey committed
175
176
177
178
179
    # the idp which authenticated the user
    idp = models.ForeignKey(
        OIDCConfig,
        related_name='users',
        on_delete=models.CASCADE,
180
181
182
        blank=True,
        null=True,
        editable=False,
Lukas Burgey's avatar
Lukas Burgey committed
183
    )
184
185
186
187
    userinfo = JSONField(
        default=user_info_default,
        null=True,
        blank=True,
188
        editable=False,
189
    )
Lukas Burgey's avatar
Lukas Burgey committed
190

Lukas Burgey's avatar
Lukas Burgey committed
191
    # we hide deleted keys here
192
    # the full list of ssh keys is self._ssh_keys
Lukas Burgey's avatar
Lukas Burgey committed
193
194
195
196
    @property
    def ssh_keys(self):
        return self._ssh_keys.filter(deleted=False)

197
198
199
200
    @property
    def is_active_at_clients(self):
        return self._is_active

201
202
203
    def __str__(self):
        if self.user_type == 'admin':
            return 'ADMIN {}'.format(self.username)
Lukas Burgey's avatar
Lukas Burgey committed
204
        elif self.user_type == 'oidcuser':
205
206
207
            if not self.is_active:
                return 'DEACTIVATED USER {}'.format(self.username)
            return 'USER {}'.format(self.username)
Lukas Burgey's avatar
Lukas Burgey committed
208
        elif self.user_type == 'apiclient':
209
210
211
212
213
            try:
                return 'APICLIENT {}@{}'.format(self.username, self.site)
            except:
                return 'APICLIENT {}'.format(self.username)

Lukas Burgey's avatar
Lukas Burgey committed
214
215
        else:
            raise Exception()
Lukas Burgey's avatar
Lukas Burgey committed
216

Lukas Burgey's avatar
Lukas Burgey committed
217
    def msg(self, msg):
218
219
220
221
222
223
224
        return '[{}] {}'.format(self, msg)

    # oidcuser: withdraw and delete all credentials and delete the user
    def remove(self):
        if self.user_type == 'oidcuser':
            self.deactivate()

Lukas Burgey's avatar
Lukas Burgey committed
225
            # FIXME: deleting the user brings problems:
226
227
            # the deletion cascades down to DeploymentTask and DeploymentTaskItem
            # but these need to be conserved so all clients withdrawals can be tracked
Lukas Burgey's avatar
Lukas Burgey committed
228
            LOGGER.info(self.msg('Deleting'))
229
230
231
            self.delete()

    def activate(self):
232
        if self._is_active:
Lukas Burgey's avatar
Lukas Burgey committed
233
            LOGGER.error(self.msg('already activated'))
234
235
236
237
            return

        if self.user_type == 'oidcuser':
            self.is_active = True
238
            self._is_active = True
239
240
241
242
243
            self.save()

            for dep in self.deployments.all():
                dep.activate()

Lukas Burgey's avatar
Lukas Burgey committed
244
            LOGGER.info(self.msg('activated'))
245
246
247

    # oidcuser: withdraw all credentials
    def deactivate(self):
248
        if not self._is_active:
Lukas Burgey's avatar
Lukas Burgey committed
249
            LOGGER.error(self.msg('already deactivated'))
250
251
252
253
            return

        if self.user_type == 'oidcuser':
            self.is_active = False
254
            self._is_active = False
255
256
257
258
259
            self.save()

            for dep in self.deployments.all():
                dep.deactivate()

Lukas Burgey's avatar
Lukas Burgey committed
260
            LOGGER.info(self.msg('deactivated'))
261
262


263
264
265
266
267
268
269
270
271
272
    @classmethod
    def construct_from_user_info(cls, user_info, idp):
        LOGGER.debug('User: constructing from %s', user_info)
        return cls(
            sub=user_info.get('sub', ''),
            first_name=user_info.get('given_name', ''),
            last_name=user_info.get('family_name', ''),
            email=user_info.get('email', ''),
            username=user_info.get('email', ''),
            idp=idp,
273
            userinfo=user_info,
274
        )
Lukas Burgey's avatar
Lukas Burgey committed
275
276
277


class Site(models.Model):
278
    client = models.OneToOneField(
Lukas Burgey's avatar
Lukas Burgey committed
279
280
281
        User,
        related_name='site',
    )
Lukas Burgey's avatar
Lukas Burgey committed
282
283
284
285
286
287
    name = models.CharField(max_length=150, unique=True)
    description = models.TextField(max_length=300, blank=True)

    def __str__(self):
        return self.name

288
289
290
291
292
293
    # tasks which are still to be executed on this site
    @property
    def tasks(self):
        return [item.task
                for item
                in self.task_items.all()]
Lukas Burgey's avatar
Lukas Burgey committed
294

Lukas Burgey's avatar
Lukas Burgey committed
295
296
297
298

class Service(models.Model):
    name = models.CharField(max_length=150, unique=True)
    description = models.TextField(max_length=300, blank=True)
299
    site = models.ManyToManyField(
Lukas Burgey's avatar
Lukas Burgey committed
300
301
        Site,
        related_name='services')
Lukas Burgey's avatar
Lukas Burgey committed
302
    groups = models.ManyToManyField(
Lukas Burgey's avatar
Lukas Burgey committed
303
304
305
        Group,
        related_name='services',
        blank=True)
Lukas Burgey's avatar
Lukas Burgey committed
306

307
308
309
310
    @property
    def routing_key(self):
        return 'service.{}'.format(self.name)

Lukas Burgey's avatar
Lukas Burgey committed
311
    def __str__(self):
Lukas Burgey's avatar
Lukas Burgey committed
312
        return self.name
Lukas Burgey's avatar
Lukas Burgey committed
313
314
315


class SSHPublicKey(models.Model):
Lukas Burgey's avatar
Lukas Burgey committed
316
317
318
319
320
321
    name = models.CharField(
        max_length=150,
    )
    key = models.TextField(
        max_length=1000
    )
Lukas Burgey's avatar
Lukas Burgey committed
322
    # hidden field at the user
Lukas Burgey's avatar
Lukas Burgey committed
323
    user = models.ForeignKey(
Lukas Burgey's avatar
Lukas Burgey committed
324
325
326
        User,
        related_name='_ssh_keys',
    )
Lukas Burgey's avatar
Lukas Burgey committed
327

Lukas Burgey's avatar
Lukas Burgey committed
328
329
    # has the user triggered the deletion of this key
    deleted = models.BooleanField(
Lukas Burgey's avatar
Lukas Burgey committed
330
331
332
        default=False,
        editable=False,
    )
333

Lukas Burgey's avatar
Lukas Burgey committed
334
    def msg(self, msg):
Lukas Burgey's avatar
Lukas Burgey committed
335
        return '[SSHPublicKey:{}] {}'.format(self, msg)
336

337
338
339
    # does not directly delete the key if the key is deployed or withdrawn
    # somewhere
    # the receiver 'delete_withdrawn_ssh_key' does the actual deletion
340
    def delete_key(self):
Lukas Burgey's avatar
Lukas Burgey committed
341
        if (not self.tasks.exists() and not self.deployments.exists()):
Lukas Burgey's avatar
Lukas Burgey committed
342
            LOGGER.info(self.msg('Direct deletion of key'))
343
344
345
            self.delete()
            return

Lukas Burgey's avatar
Lukas Burgey committed
346
        LOGGER.info(self.msg('Deletion of key started'))
347
348
349
        self.deleted = True
        self.save()

Lukas Burgey's avatar
Lukas Burgey committed
350
        # delete implies withdrawing the key from all clients
351
352
353
        for deployment in self.deployments.all():
            deployment.withdraw_key(self)

Lukas Burgey's avatar
Lukas Burgey committed
354
355
    # when a key is withdrawn by a client we try to finally delete it
    def try_final_deletion(self):
Lukas Burgey's avatar
Lukas Burgey committed
356
        if (self.deleted and not self.tasks.exists()):
Lukas Burgey's avatar
Lukas Burgey committed
357
            LOGGER.info(self.msg(
Lukas Burgey's avatar
Lukas Burgey committed
358
                'All clients have withdrawn this key. Final deletion'))
Lukas Burgey's avatar
Lukas Burgey committed
359
360
361
            self.delete()
            return

Lukas Burgey's avatar
Lukas Burgey committed
362
    def __str__(self):
Lukas Burgey's avatar
Lukas Burgey committed
363
364
        if self.deleted:
            return "DELETED: {}".format(self.name)
Lukas Burgey's avatar
Lukas Burgey committed
365
366
367
        return self.name


368
# Deployment describes the credential state per user as it is supposed to be
369
370
371
372
#
# (exception: if is_active=False the ssh_keys contain the keys to be deployed
# if the deployment is reactivated)
#
373
374
# DeploymentTask is what is sent to the clients via rabbitmq
# The DeploymentTaskItem track the acknowledgements from the clients
Lukas Burgey's avatar
Lukas Burgey committed
375
376
class Deployment(models.Model):
    user = models.ForeignKey(
Lukas Burgey's avatar
Lukas Burgey committed
377
378
379
380
        User,
        related_name='deployments',
        on_delete=models.CASCADE,
    )
Lukas Burgey's avatar
Lukas Burgey committed
381
    service = models.ForeignKey(
Lukas Burgey's avatar
Lukas Burgey committed
382
383
384
385
        Service,
        related_name='deployments',
        on_delete=models.CASCADE,
    )
Lukas Burgey's avatar
Lukas Burgey committed
386
    ssh_keys = models.ManyToManyField(
Lukas Burgey's avatar
Lukas Burgey committed
387
388
389
390
        SSHPublicKey,
        related_name='deployments',
        blank=True,
    )
391
    ssh_keys_to_withdraw = models.ManyToManyField(
Lukas Burgey's avatar
Lukas Burgey committed
392
393
394
395
        SSHPublicKey,
        related_name='withdrawn_deployments',
        blank=True,
    )
396
    is_active = models.BooleanField(
Lukas Burgey's avatar
Lukas Burgey committed
397
398
        default=True,
    )
399

400
401
402
    @property
    def withdrawals(self):
        return self.tasks.filter(action='withdraw')
Lukas Burgey's avatar
Lukas Burgey committed
403

404
405
406
    @property
    def deploys(self):
        return self.tasks.filter(action='deploy')
Lukas Burgey's avatar
Lukas Burgey committed
407

408
409
    def __str__(self):
        return '{}:{}'.format(self.service, self.user)
410

Lukas Burgey's avatar
Lukas Burgey committed
411
    def msg(self, msg):
412
        return '[Deployment:{}] {}'.format(self, msg)
413

414
415
416
    # deploy credentials which were deployed prior to deactivation
    def activate(self):
        if self.is_active:
Lukas Burgey's avatar
Lukas Burgey committed
417
            LOGGER.error(self.msg('already active'))
418
419
            return

Lukas Burgey's avatar
Lukas Burgey committed
420
        LOGGER.debug(self.msg(str(self.ssh_keys.all())))
421
422
423
424
425
        for key in self.ssh_keys.all():
            self._deploy_key(key)

        self.is_active = True
        self.save()
Lukas Burgey's avatar
Lukas Burgey committed
426
        LOGGER.info(self.msg('activated'))
427
428
429
430

    # withdraw all credentials
    def deactivate(self):
        if not self.is_active:
Lukas Burgey's avatar
Lukas Burgey committed
431
            LOGGER.error(self.msg('already deactivated'))
432
433
434
            return

        self.is_active = False
435
        self.save()
436

437
438
439
        for key in self.ssh_keys.all():
            self._withdraw_key(key)

Lukas Burgey's avatar
Lukas Burgey committed
440
        LOGGER.info(self.msg('deactivated'))
441
442
443
444

    # only deploy the key
    def _deploy_key(self, key):
        # delete outstanding tasks which are made obsolete by this task
445
        for withdrawal in self.withdrawals.filter(key=key):
Lukas Burgey's avatar
Lukas Burgey committed
446
            LOGGER.debug(withdrawal.msg('now obsolete'))
Lukas Burgey's avatar
Lukas Burgey committed
447
            withdrawal.delete()
448
449
450

        # generate task
        task = DeploymentTask(
Lukas Burgey's avatar
Lukas Burgey committed
451
452
453
454
            action='deploy',
            deployment=self,
            key=key,
        )
455
        task.save()
Lukas Burgey's avatar
Lukas Burgey committed
456
        LOGGER.debug(task.msg('generated'))
457
458
459
460

        # generate task items
        for site in self.service.site.all():
            deploy = DeploymentTaskItem(
Lukas Burgey's avatar
Lukas Burgey committed
461
462
463
                task=task,
                site=site,
            )
464
            deploy.save()
Lukas Burgey's avatar
Lukas Burgey committed
465
            LOGGER.debug(deploy.msg('generated'))
466
467
468

        # publish the task
        task.publish()
469

470
471
    def _withdraw_key(self, key):
        # delete outstanding tasks which are made obsolete by this task
472
        for deploy in self.deploys.filter(key=key):
Lukas Burgey's avatar
Lukas Burgey committed
473
            LOGGER.debug(deploy.msg("now obsolete"))
Lukas Burgey's avatar
Lukas Burgey committed
474
            deploy.delete()
Lukas Burgey's avatar
Lukas Burgey committed
475

476
477
        # generate task
        task = DeploymentTask(
Lukas Burgey's avatar
Lukas Burgey committed
478
479
480
481
            action='withdraw',
            deployment=self,
            key=key,
        )
482
        task.save()
Lukas Burgey's avatar
Lukas Burgey committed
483
        LOGGER.debug(task.msg('generated'))
Lukas Burgey's avatar
Lukas Burgey committed
484

485
486
487
        # generate task items
        for site in self.service.site.all():
            withdrawal = DeploymentTaskItem(
Lukas Burgey's avatar
Lukas Burgey committed
488
489
490
                task=task,
                site=site,
            )
491
            withdrawal.save()
Lukas Burgey's avatar
Lukas Burgey committed
492
            LOGGER.debug(withdrawal.msg('generated'))
493

494
495
        # publish the task
        task.publish()
Lukas Burgey's avatar
Lukas Burgey committed
496

497
498
499
    # deploy key and track changes in the key lists
    def deploy_key(self, key):
        if not self.is_active:
Lukas Burgey's avatar
Lukas Burgey committed
500
            LOGGER.error(self.msg('cannot deploy while deactivated'))
501
502
503
504
505
506
507
508
509
510
511
512
513
            raise Exception('deployment deactivated')

        self.ssh_keys.add(key)

        if key in self.ssh_keys_to_withdraw.all():
            self.ssh_keys_to_withdraw.remove(key)
        self.save()

        self._deploy_key(key)

    # withdraw key and track changes in the key lists
    def withdraw_key(self, key):
        if not self.is_active:
Lukas Burgey's avatar
Lukas Burgey committed
514
            LOGGER.error(self.msg('cannot withdraw while deactivated'))
515
516
517
518
519
520
521
522
523
524
            raise Exception('deployment deactivated')

        self.ssh_keys.remove(key)

        # keys which are to be withdrawn by the clients
        self.ssh_keys_to_withdraw.add(key)
        self.save()

        self._withdraw_key(key)

Lukas Burgey's avatar
Lukas Burgey committed
525

526
527
class DeploymentTask(models.Model):
    ACTION_CHOICES = (
Lukas Burgey's avatar
Lukas Burgey committed
528
529
530
        ('deploy', 'deploy'),
        ('withdraw', 'withdraw'),
    )
531
    action = models.CharField(
Lukas Burgey's avatar
Lukas Burgey committed
532
533
534
        max_length=10,
        choices=ACTION_CHOICES,
    )
535
    key = models.ForeignKey(
Lukas Burgey's avatar
Lukas Burgey committed
536
537
538
539
        SSHPublicKey,
        related_name='tasks',
        on_delete=models.CASCADE,
    )
Lukas Burgey's avatar
Lukas Burgey committed
540
    deployment = models.ForeignKey(
Lukas Burgey's avatar
Lukas Burgey committed
541
542
543
544
        Deployment,
        related_name='tasks',
        on_delete=models.CASCADE,
    )
Lukas Burgey's avatar
Lukas Burgey committed
545
546
547
548
549
550
551
552
553

    @property
    def user(self):
        return self.deployment.user

    @property
    def service(self):
        return self.deployment.service

554
    def __str__(self):
555
        return "{}:{}:{} - {}#{}".format(
Lukas Burgey's avatar
Lukas Burgey committed
556
557
558
559
            self.deployment.service,
            self.deployment.user,
            self.key,
            self.action,
560
            self.id,
Lukas Burgey's avatar
Lukas Burgey committed
561
        )
562

Lukas Burgey's avatar
Lukas Burgey committed
563
    def msg(self, msg):
Lukas Burgey's avatar
Lukas Burgey committed
564
        return '[DeploymentTask:{}] {}'.format(self, msg)
565
566

    def publish(self):
567
        # FIXME mitigating circular dependencies here
568
569
570
        from .clientapi.serializers import DeploymentTaskSerializer
        msg = json.dumps(DeploymentTaskSerializer(self).data)

571
        RabbitMQInstance.load().publish_by_service(
Lukas Burgey's avatar
Lukas Burgey committed
572
573
574
            self.service,
            msg,
        )
575
576
577

    # the client acked the receipt and execution of the task for his site
    def item_finished(self, site):
Lukas Burgey's avatar
Lukas Burgey committed
578
        item = self.task_items.get(site=site)
Lukas Burgey's avatar
Lukas Burgey committed
579
        LOGGER.debug(item.msg('done'))
Lukas Burgey's avatar
Lukas Burgey committed
580
        item.delete()
581
582
583
584
585
586

        if not self.task_items.exists():
            self.finished()

    # maintenance after all task items are done
    def finished(self):
Lukas Burgey's avatar
Lukas Burgey committed
587
        LOGGER.info(self.msg('done'))
Lukas Burgey's avatar
Lukas Burgey committed
588
        self.delete()
589
590
591
592
593
594
595
596

        # check if this was the final withdraw in a key deletion
        if self.action == 'withdraw':
            self.key.try_final_deletion()


class DeploymentTaskItem(models.Model):
    task = models.ForeignKey(
Lukas Burgey's avatar
Lukas Burgey committed
597
598
599
600
        DeploymentTask,
        related_name='task_items',
        on_delete=models.CASCADE,
    )
601
    site = models.ForeignKey(
Lukas Burgey's avatar
Lukas Burgey committed
602
603
604
605
        Site,
        related_name='task_items',
        on_delete=models.CASCADE,
    )
606

Lukas Burgey's avatar
Lukas Burgey committed
607
    def __str__(self):
608
        return "{}@{}#{}".format(
Lukas Burgey's avatar
Lukas Burgey committed
609
610
            self.task,
            self.site,
611
            self.id,
Lukas Burgey's avatar
Lukas Burgey committed
612
        )
613

Lukas Burgey's avatar
Lukas Burgey committed
614
    def msg(self, msg):
Lukas Burgey's avatar
Lukas Burgey committed
615
        return '[DeploymentTaskItem:{}] {}'.format(self, msg)
616

Lukas Burgey's avatar
Lukas Burgey committed
617

618
619
620
#
# RECEIVERS
#
Lukas Burgey's avatar
Lukas Burgey committed
621

622
623
624
625
#@receiver(post_save, sender=settings.AUTH_USER_MODEL)
#def create_auth_token(sender, instance=None, created=False, **kwargs):
#    if instance.user_type == 'apiclient' and created:
#        Token.objects.create(user=instance)
Lukas Burgey's avatar
Lukas Burgey committed
626
627


628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
#@receiver(post_save, sender=Site)
#def register_at_rabbitmq(sender, instance=None, created=False, **kwargs):
#    if not created:
#        return
#
#    RabbitMQInstance.load().register_site(instance)
#
#
#@receiver(pre_delete, sender=Site)
#def deregister_at_rabbitmq(sender, instance=None, **kwargs):
#    RabbitMQInstance.load().deregister_site(instance)
#
#
#@receiver(post_save, sender=Service)
#def update_at_rabbitmq(sender, instance=None, **kwargs):
#    for site in instance.site.all():
#        RabbitMQInstance.load().update_site(site)
645
646
647
648
649
650
651


@receiver(post_save, sender=User)
def deactivate_user(sender, instance=None, created=False, **kwargs):
    if created:
        return

652
    if not instance.is_active and instance.is_active_at_clients:
653
654
655
656
657
658
659
660
        instance.deactivate()


@receiver(post_save, sender=User)
def activate_user(sender, instance=None, created=False, **kwargs):
    if created:
        return

661
    if instance.is_active and not instance.is_active_at_clients:
662
        instance.activate()