mirror of https://github.com/jumpserver/jumpserver
perf: Account backup
parent
707a83ec19
commit
9598174745
|
@ -1,41 +1,31 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
#
|
#
|
||||||
from rest_framework import status, viewsets
|
|
||||||
from rest_framework.response import Response
|
|
||||||
|
|
||||||
from accounts import serializers
|
from accounts import serializers
|
||||||
|
from accounts.const import AutomationTypes
|
||||||
from accounts.models import (
|
from accounts.models import (
|
||||||
AccountBackupAutomation, AccountBackupExecution
|
BackupAccountAutomation
|
||||||
)
|
)
|
||||||
from accounts.tasks import execute_account_backup_task
|
|
||||||
from common.const.choices import Trigger
|
|
||||||
from orgs.mixins.api import OrgBulkModelViewSet
|
from orgs.mixins.api import OrgBulkModelViewSet
|
||||||
|
from .base import AutomationExecutionViewSet
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
'AccountBackupPlanViewSet', 'AccountBackupPlanExecutionViewSet'
|
'AccountBackupPlanViewSet', 'BackupAccountExecutionViewSet'
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
class AccountBackupPlanViewSet(OrgBulkModelViewSet):
|
class AccountBackupPlanViewSet(OrgBulkModelViewSet):
|
||||||
model = AccountBackupAutomation
|
model = BackupAccountAutomation
|
||||||
filterset_fields = ('name',)
|
filterset_fields = ('name',)
|
||||||
search_fields = filterset_fields
|
search_fields = filterset_fields
|
||||||
serializer_class = serializers.AccountBackupSerializer
|
serializer_class = serializers.BackupAccountSerializer
|
||||||
|
|
||||||
|
|
||||||
class AccountBackupPlanExecutionViewSet(viewsets.ModelViewSet):
|
class BackupAccountExecutionViewSet(AutomationExecutionViewSet):
|
||||||
serializer_class = serializers.AccountBackupPlanExecutionSerializer
|
serializer_class = serializers.BackupAccountExecutionSerializer
|
||||||
search_fields = ('trigger', 'plan__name')
|
|
||||||
filterset_fields = ('trigger', 'plan_id', 'plan__name')
|
|
||||||
http_method_names = ['get', 'post', 'options']
|
http_method_names = ['get', 'post', 'options']
|
||||||
|
tp = AutomationTypes.backup_account
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
queryset = AccountBackupExecution.objects.all()
|
queryset = super().get_queryset()
|
||||||
|
queryset = queryset.filter(automation__type=self.tp)
|
||||||
return queryset
|
return queryset
|
||||||
|
|
||||||
def create(self, request, *args, **kwargs):
|
|
||||||
serializer = self.get_serializer(data=request.data)
|
|
||||||
serializer.is_valid(raise_exception=True)
|
|
||||||
pid = serializer.data.get('plan')
|
|
||||||
task = execute_account_backup_task.delay(pid=str(pid), trigger=Trigger.manual)
|
|
||||||
return Response({'task': task.id}, status=status.HTTP_201_CREATED)
|
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
#
|
#
|
||||||
from django.db import transaction
|
|
||||||
from django.http import HttpResponse
|
from django.http import HttpResponse
|
||||||
from django.shortcuts import get_object_or_404
|
from django.shortcuts import get_object_or_404
|
||||||
from rest_framework import status
|
from rest_framework import status
|
||||||
|
@ -13,7 +12,6 @@ from accounts.filters import GatheredAccountFilterSet
|
||||||
from accounts.models import GatherAccountsAutomation, AutomationExecution
|
from accounts.models import GatherAccountsAutomation, AutomationExecution
|
||||||
from accounts.models import GatheredAccount
|
from accounts.models import GatheredAccount
|
||||||
from assets.models import Asset
|
from assets.models import Asset
|
||||||
from accounts.tasks.common import quickstart_automation_by_snapshot
|
|
||||||
from orgs.mixins.api import OrgBulkModelViewSet
|
from orgs.mixins.api import OrgBulkModelViewSet
|
||||||
from .base import AutomationExecutionViewSet
|
from .base import AutomationExecutionViewSet
|
||||||
|
|
||||||
|
|
|
@ -8,7 +8,7 @@ from rest_framework import serializers
|
||||||
from xlsxwriter import Workbook
|
from xlsxwriter import Workbook
|
||||||
|
|
||||||
from accounts.const import AccountBackupType
|
from accounts.const import AccountBackupType
|
||||||
from accounts.models.automations.backup_account import AccountBackupAutomation
|
from accounts.models.automations.backup_account import BackupAccountAutomation
|
||||||
from accounts.notifications import AccountBackupExecutionTaskMsg, AccountBackupByObjStorageExecutionTaskMsg
|
from accounts.notifications import AccountBackupExecutionTaskMsg, AccountBackupByObjStorageExecutionTaskMsg
|
||||||
from accounts.serializers import AccountSecretSerializer
|
from accounts.serializers import AccountSecretSerializer
|
||||||
from assets.const import AllTypes
|
from assets.const import AllTypes
|
||||||
|
@ -20,6 +20,7 @@ from users.models import User
|
||||||
PATH = os.path.join(os.path.dirname(settings.BASE_DIR), 'tmp')
|
PATH = os.path.join(os.path.dirname(settings.BASE_DIR), 'tmp')
|
||||||
split_help_text = _('The account key will be split into two parts and sent')
|
split_help_text = _('The account key will be split into two parts and sent')
|
||||||
|
|
||||||
|
|
||||||
class RecipientsNotFound(Exception):
|
class RecipientsNotFound(Exception):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@ -176,8 +177,7 @@ class AccountBackupHandler:
|
||||||
encrypt_and_compress_zip_file(attachment, user.secret_key, files)
|
encrypt_and_compress_zip_file(attachment, user.secret_key, files)
|
||||||
attachment_list = [attachment, ]
|
attachment_list = [attachment, ]
|
||||||
AccountBackupExecutionTaskMsg(plan_name, user).publish(attachment_list)
|
AccountBackupExecutionTaskMsg(plan_name, user).publish(attachment_list)
|
||||||
email_sent_to = _('Email sent to')
|
|
||||||
print('{} {}({})'.format(email_sent_to, user, user.email))
|
|
||||||
for file in files:
|
for file in files:
|
||||||
os.remove(file)
|
os.remove(file)
|
||||||
|
|
||||||
|
@ -210,15 +210,6 @@ class AccountBackupHandler:
|
||||||
self.execution.reason = reason[:1024]
|
self.execution.reason = reason[:1024]
|
||||||
self.execution.is_success = is_success
|
self.execution.is_success = is_success
|
||||||
self.execution.save()
|
self.execution.save()
|
||||||
finish = _('Finish')
|
|
||||||
print(f'\n{finish}\n')
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def step_finished(is_success):
|
|
||||||
if is_success:
|
|
||||||
print(_('Success'))
|
|
||||||
else:
|
|
||||||
print(_('Failed'))
|
|
||||||
|
|
||||||
def _run(self):
|
def _run(self):
|
||||||
is_success = False
|
is_success = False
|
||||||
|
@ -238,11 +229,10 @@ class AccountBackupHandler:
|
||||||
finally:
|
finally:
|
||||||
reason = error
|
reason = error
|
||||||
self.step_perform_task_update(is_success, reason)
|
self.step_perform_task_update(is_success, reason)
|
||||||
self.step_finished(is_success)
|
|
||||||
|
|
||||||
def backup_by_obj_storage(self):
|
def backup_by_obj_storage(self):
|
||||||
object_id = self.execution.snapshot.get('id')
|
object_id = self.execution.snapshot.get('id')
|
||||||
zip_encrypt_password = AccountBackupAutomation.objects.get(id=object_id).zip_encrypt_password
|
zip_encrypt_password = BackupAccountAutomation.objects.get(id=object_id).zip_encrypt_password
|
||||||
obj_recipients_part_one = self.execution.snapshot.get('obj_recipients_part_one', [])
|
obj_recipients_part_one = self.execution.snapshot.get('obj_recipients_part_one', [])
|
||||||
obj_recipients_part_two = self.execution.snapshot.get('obj_recipients_part_two', [])
|
obj_recipients_part_two = self.execution.snapshot.get('obj_recipients_part_two', [])
|
||||||
no_assigned_sftp_server = _('The backup task has no assigned sftp server')
|
no_assigned_sftp_server = _('The backup task has no assigned sftp server')
|
||||||
|
@ -266,7 +256,6 @@ class AccountBackupHandler:
|
||||||
self.send_backup_obj_storage(files, recipients, zip_encrypt_password)
|
self.send_backup_obj_storage(files, recipients, zip_encrypt_password)
|
||||||
|
|
||||||
def backup_by_email(self):
|
def backup_by_email(self):
|
||||||
|
|
||||||
warn_text = _('The backup task has no assigned recipient')
|
warn_text = _('The backup task has no assigned recipient')
|
||||||
recipients_part_one = self.execution.snapshot.get('recipients_part_one', [])
|
recipients_part_one = self.execution.snapshot.get('recipients_part_one', [])
|
||||||
recipients_part_two = self.execution.snapshot.get('recipients_part_two', [])
|
recipients_part_two = self.execution.snapshot.get('recipients_part_two', [])
|
||||||
|
@ -291,7 +280,6 @@ class AccountBackupHandler:
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
plan_start = _('Plan start')
|
plan_start = _('Plan start')
|
||||||
plan_end = _('Plan end')
|
|
||||||
time_cost = _('Duration')
|
time_cost = _('Duration')
|
||||||
error = _('An exception occurred during task execution')
|
error = _('An exception occurred during task execution')
|
||||||
print('{}: {}'.format(plan_start, local_now_display()))
|
print('{}: {}'.format(plan_start, local_now_display()))
|
||||||
|
@ -302,6 +290,5 @@ class AccountBackupHandler:
|
||||||
print(error)
|
print(error)
|
||||||
print(e)
|
print(e)
|
||||||
finally:
|
finally:
|
||||||
print('\n{}: {}'.format(plan_end, local_now_display()))
|
|
||||||
timedelta = round((time.time() - time_start), 2)
|
timedelta = round((time.time() - time_start), 2)
|
||||||
print('{}: {}s'.format(time_cost, timedelta))
|
print('{}: {}s'.format(time_cost, timedelta))
|
||||||
|
|
|
@ -28,13 +28,14 @@ class AutomationTypes(models.TextChoices):
|
||||||
gather_accounts = 'gather_accounts', _('Gather accounts')
|
gather_accounts = 'gather_accounts', _('Gather accounts')
|
||||||
verify_gateway_account = 'verify_gateway_account', _('Verify gateway account')
|
verify_gateway_account = 'verify_gateway_account', _('Verify gateway account')
|
||||||
check_account = 'check_account', _('Check account')
|
check_account = 'check_account', _('Check account')
|
||||||
|
backup_account = 'backup_account', _('Backup account')
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_type_model(cls, tp):
|
def get_type_model(cls, tp):
|
||||||
from accounts.models import (
|
from accounts.models import (
|
||||||
PushAccountAutomation, ChangeSecretAutomation,
|
PushAccountAutomation, ChangeSecretAutomation,
|
||||||
VerifyAccountAutomation, GatherAccountsAutomation,
|
VerifyAccountAutomation, GatherAccountsAutomation,
|
||||||
CheckAccountAutomation,
|
CheckAccountAutomation, BackupAccountAutomation
|
||||||
)
|
)
|
||||||
type_model_dict = {
|
type_model_dict = {
|
||||||
cls.push_account: PushAccountAutomation,
|
cls.push_account: PushAccountAutomation,
|
||||||
|
@ -42,6 +43,7 @@ class AutomationTypes(models.TextChoices):
|
||||||
cls.verify_account: VerifyAccountAutomation,
|
cls.verify_account: VerifyAccountAutomation,
|
||||||
cls.gather_accounts: GatherAccountsAutomation,
|
cls.gather_accounts: GatherAccountsAutomation,
|
||||||
cls.check_account: CheckAccountAutomation,
|
cls.check_account: CheckAccountAutomation,
|
||||||
|
cls.backup_account: BackupAccountAutomation,
|
||||||
}
|
}
|
||||||
return type_model_dict.get(tp)
|
return type_model_dict.get(tp)
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,116 @@
|
||||||
|
# Generated by Django 4.1.13 on 2024-12-03 09:23
|
||||||
|
from datetime import timedelta as dt_timedelta
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
import common.db.fields
|
||||||
|
|
||||||
|
|
||||||
|
def migrate_account_backup(apps, schema_editor):
|
||||||
|
old_backup_model = apps.get_model('accounts', 'AccountBackupAutomation')
|
||||||
|
account_backup_model = apps.get_model('accounts', 'BackupAccountAutomation')
|
||||||
|
backup_id_old_new_map = {}
|
||||||
|
for backup in old_backup_model.objects.all():
|
||||||
|
data = {
|
||||||
|
'comment': backup.comment,
|
||||||
|
'created_by': backup.created_by,
|
||||||
|
'updated_by': backup.updated_by,
|
||||||
|
'date_created': backup.date_created,
|
||||||
|
'date_updated': backup.date_updated,
|
||||||
|
'name': backup.name,
|
||||||
|
'interval': backup.interval,
|
||||||
|
'crontab': backup.crontab,
|
||||||
|
'is_periodic': backup.is_periodic,
|
||||||
|
'start_time': backup.start_time,
|
||||||
|
'date_last_run': backup.date_last_run,
|
||||||
|
'org_id': backup.org_id,
|
||||||
|
'type': 'backup_account',
|
||||||
|
'types': backup.types,
|
||||||
|
'backup_type': backup.backup_type,
|
||||||
|
'is_password_divided_by_email': backup.is_password_divided_by_email,
|
||||||
|
'is_password_divided_by_obj_storage': backup.is_password_divided_by_obj_storage,
|
||||||
|
'zip_encrypt_password': backup.zip_encrypt_password
|
||||||
|
}
|
||||||
|
obj = account_backup_model.objects.create(**data)
|
||||||
|
backup_id_old_new_map[str(backup.id)] = str(obj.id)
|
||||||
|
obj.recipients_part_one.set(backup.recipients_part_one.all())
|
||||||
|
obj.recipients_part_two.set(backup.recipients_part_two.all())
|
||||||
|
obj.obj_recipients_part_one.set(backup.obj_recipients_part_one.all())
|
||||||
|
obj.obj_recipients_part_two.set(backup.obj_recipients_part_two.all())
|
||||||
|
|
||||||
|
old_execution_model = apps.get_model('accounts', 'AccountBackupExecution')
|
||||||
|
backup_execution_model = apps.get_model('accounts', 'AutomationExecution')
|
||||||
|
|
||||||
|
for execution in old_execution_model.objects.all():
|
||||||
|
automation_id = backup_id_old_new_map.get(str(execution.plan_id))
|
||||||
|
if not automation_id:
|
||||||
|
continue
|
||||||
|
data = {
|
||||||
|
'automation_id': automation_id,
|
||||||
|
'date_start': execution.date_start,
|
||||||
|
'duration': int(execution.timedelta),
|
||||||
|
'date_finished': execution.date_start + dt_timedelta(seconds=int(execution.timedelta)),
|
||||||
|
'snapshot': execution.snapshot,
|
||||||
|
'trigger': execution.trigger,
|
||||||
|
'status': 'error' if execution.reason == '-' else 'success',
|
||||||
|
'org_id': execution.org_id
|
||||||
|
}
|
||||||
|
backup_execution_model.objects.create(**data)
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
dependencies = [
|
||||||
|
('assets', '0010_alter_automationexecution_duration'),
|
||||||
|
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||||
|
('terminal', '0003_auto_20171230_0308'),
|
||||||
|
('accounts', '0018_changesecretrecord_ignore_fail_and_more'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='BackupAccountAutomation',
|
||||||
|
fields=[
|
||||||
|
('baseautomation_ptr',
|
||||||
|
models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True,
|
||||||
|
primary_key=True, serialize=False, to='assets.baseautomation')),
|
||||||
|
('types', models.JSONField(default=list)),
|
||||||
|
('backup_type',
|
||||||
|
models.CharField(choices=[('email', 'Email'), ('object_storage', 'SFTP')], default='email',
|
||||||
|
max_length=128, verbose_name='Backup type')),
|
||||||
|
('is_password_divided_by_email', models.BooleanField(default=True, verbose_name='Password divided')),
|
||||||
|
('is_password_divided_by_obj_storage',
|
||||||
|
models.BooleanField(default=True, verbose_name='Password divided')),
|
||||||
|
('zip_encrypt_password', common.db.fields.EncryptCharField(blank=True, max_length=4096, null=True,
|
||||||
|
verbose_name='Zip encrypt password')),
|
||||||
|
('obj_recipients_part_one',
|
||||||
|
models.ManyToManyField(blank=True, related_name='obj_recipient_part_one_plans',
|
||||||
|
to='terminal.replaystorage', verbose_name='Object storage recipient part one')),
|
||||||
|
('obj_recipients_part_two',
|
||||||
|
models.ManyToManyField(blank=True, related_name='obj_recipient_part_two_plans',
|
||||||
|
to='terminal.replaystorage', verbose_name='Object storage recipient part two')),
|
||||||
|
('recipients_part_one', models.ManyToManyField(blank=True, related_name='recipient_part_one_plans',
|
||||||
|
to=settings.AUTH_USER_MODEL,
|
||||||
|
verbose_name='Recipient part one')),
|
||||||
|
('recipients_part_two', models.ManyToManyField(blank=True, related_name='recipient_part_two_plans',
|
||||||
|
to=settings.AUTH_USER_MODEL,
|
||||||
|
verbose_name='Recipient part two')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'Account backup plan',
|
||||||
|
},
|
||||||
|
bases=('accounts.accountbaseautomation',),
|
||||||
|
),
|
||||||
|
migrations.RunPython(migrate_account_backup),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='accountbackupexecution',
|
||||||
|
name='plan',
|
||||||
|
),
|
||||||
|
migrations.DeleteModel(
|
||||||
|
name='AccountBackupAutomation',
|
||||||
|
),
|
||||||
|
migrations.DeleteModel(
|
||||||
|
name='AccountBackupExecution',
|
||||||
|
),
|
||||||
|
]
|
|
@ -1,30 +1,26 @@
|
||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
#
|
#
|
||||||
import uuid
|
|
||||||
|
|
||||||
from celery import current_task
|
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.db.models import F
|
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
from accounts.const import AccountBackupType
|
from accounts.const import AccountBackupType, AutomationTypes
|
||||||
from common.const.choices import Trigger
|
|
||||||
from common.db import fields
|
from common.db import fields
|
||||||
from common.db.encoder import ModelJSONFieldEncoder
|
from common.utils import get_logger
|
||||||
from common.utils import get_logger, lazyproperty
|
from .base import AccountBaseAutomation
|
||||||
from ops.mixin import PeriodTaskModelMixin
|
|
||||||
from orgs.mixins.models import OrgModelMixin, JMSOrgBaseModel
|
|
||||||
|
|
||||||
__all__ = ['AccountBackupAutomation', 'AccountBackupExecution']
|
__all__ = ['BackupAccountAutomation']
|
||||||
|
|
||||||
logger = get_logger(__file__)
|
logger = get_logger(__file__)
|
||||||
|
|
||||||
|
|
||||||
class AccountBackupAutomation(PeriodTaskModelMixin, JMSOrgBaseModel):
|
class BackupAccountAutomation(AccountBaseAutomation):
|
||||||
types = models.JSONField(default=list)
|
types = models.JSONField(default=list)
|
||||||
backup_type = models.CharField(max_length=128, choices=AccountBackupType.choices,
|
backup_type = models.CharField(
|
||||||
default=AccountBackupType.email.value, verbose_name=_('Backup type'))
|
max_length=128, choices=AccountBackupType.choices,
|
||||||
|
default=AccountBackupType.email, verbose_name=_('Backup type')
|
||||||
|
)
|
||||||
is_password_divided_by_email = models.BooleanField(default=True, verbose_name=_('Password divided'))
|
is_password_divided_by_email = models.BooleanField(default=True, verbose_name=_('Password divided'))
|
||||||
is_password_divided_by_obj_storage = models.BooleanField(default=True, verbose_name=_('Password divided'))
|
is_password_divided_by_obj_storage = models.BooleanField(default=True, verbose_name=_('Password divided'))
|
||||||
recipients_part_one = models.ManyToManyField(
|
recipients_part_one = models.ManyToManyField(
|
||||||
|
@ -51,27 +47,11 @@ class AccountBackupAutomation(PeriodTaskModelMixin, JMSOrgBaseModel):
|
||||||
return f'{self.name}({self.org_id})'
|
return f'{self.name}({self.org_id})'
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
ordering = ['name']
|
|
||||||
unique_together = [('name', 'org_id')]
|
|
||||||
verbose_name = _('Account backup plan')
|
verbose_name = _('Account backup plan')
|
||||||
|
|
||||||
def get_register_task(self):
|
|
||||||
from ...tasks import execute_account_backup_task
|
|
||||||
name = "account_backup_plan_period_{}".format(str(self.id)[:8])
|
|
||||||
task = execute_account_backup_task.name
|
|
||||||
args = (str(self.id), Trigger.timing)
|
|
||||||
kwargs = {}
|
|
||||||
return name, task, args, kwargs
|
|
||||||
|
|
||||||
def to_attr_json(self):
|
def to_attr_json(self):
|
||||||
return {
|
attr_json = super().to_attr_json()
|
||||||
'id': self.id,
|
attr_json.update({
|
||||||
'name': self.name,
|
|
||||||
'is_periodic': self.is_periodic,
|
|
||||||
'interval': self.interval,
|
|
||||||
'crontab': self.crontab,
|
|
||||||
'org_id': self.org_id,
|
|
||||||
'created_by': self.created_by,
|
|
||||||
'types': self.types,
|
'types': self.types,
|
||||||
'backup_type': self.backup_type,
|
'backup_type': self.backup_type,
|
||||||
'is_password_divided_by_email': self.is_password_divided_by_email,
|
'is_password_divided_by_email': self.is_password_divided_by_email,
|
||||||
|
@ -93,75 +73,41 @@ class AccountBackupAutomation(PeriodTaskModelMixin, JMSOrgBaseModel):
|
||||||
str(obj_storage.id): (str(obj_storage.name), str(obj_storage.type))
|
str(obj_storage.id): (str(obj_storage.name), str(obj_storage.type))
|
||||||
for obj_storage in self.obj_recipients_part_two.all()
|
for obj_storage in self.obj_recipients_part_two.all()
|
||||||
},
|
},
|
||||||
}
|
})
|
||||||
|
return attr_json
|
||||||
|
|
||||||
@property
|
def save(self, *args, **kwargs):
|
||||||
def executed_amount(self):
|
self.type = AutomationTypes.backup_account
|
||||||
return self.execution.count()
|
super().save(*args, **kwargs)
|
||||||
|
|
||||||
def execute(self, trigger):
|
# class AccountBackupExecution(AutomationExecution):
|
||||||
try:
|
# plan = models.ForeignKey(
|
||||||
hid = current_task.request.id
|
# 'AccountBackupAutomation', related_name='execution', on_delete=models.CASCADE,
|
||||||
except AttributeError:
|
# verbose_name=_('Account backup plan')
|
||||||
hid = str(uuid.uuid4())
|
# )
|
||||||
execution = AccountBackupExecution.objects.create(
|
#
|
||||||
id=hid, plan=self, snapshot=self.to_attr_json(), trigger=trigger
|
# class Meta:
|
||||||
)
|
# verbose_name = _('Account backup execution')
|
||||||
return execution.start()
|
#
|
||||||
|
# @property
|
||||||
@lazyproperty
|
# def types(self):
|
||||||
def latest_execution(self):
|
# types = self.snapshot.get('types')
|
||||||
return self.execution.first()
|
# return types
|
||||||
|
#
|
||||||
|
# @lazyproperty
|
||||||
class AccountBackupExecution(OrgModelMixin):
|
# def backup_accounts(self):
|
||||||
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
|
# from accounts.models import Account
|
||||||
date_start = models.DateTimeField(
|
# # TODO 可以优化一下查询 在账号上做 category 的缓存 避免数据量大时连表操作
|
||||||
auto_now_add=True, verbose_name=_('Date start')
|
# qs = Account.objects.filter(
|
||||||
)
|
# asset__platform__type__in=self.types
|
||||||
timedelta = models.FloatField(
|
# ).annotate(type=F('asset__platform__type'))
|
||||||
default=0.0, verbose_name=_('Time'), null=True
|
# return qs
|
||||||
)
|
#
|
||||||
snapshot = models.JSONField(
|
# @property
|
||||||
encoder=ModelJSONFieldEncoder, default=dict,
|
# def manager_type(self):
|
||||||
blank=True, null=True, verbose_name=_('Account backup snapshot')
|
# return 'backup_account'
|
||||||
)
|
#
|
||||||
trigger = models.CharField(
|
# def start(self):
|
||||||
max_length=128, default=Trigger.manual, choices=Trigger.choices,
|
# from accounts.automations.endpoint import ExecutionManager
|
||||||
verbose_name=_('Trigger mode')
|
# manager = ExecutionManager(execution=self)
|
||||||
)
|
# return manager.run()
|
||||||
reason = models.CharField(
|
|
||||||
max_length=1024, blank=True, null=True, verbose_name=_('Reason')
|
|
||||||
)
|
|
||||||
is_success = models.BooleanField(default=False, verbose_name=_('Is success'))
|
|
||||||
plan = models.ForeignKey(
|
|
||||||
'AccountBackupAutomation', related_name='execution', on_delete=models.CASCADE,
|
|
||||||
verbose_name=_('Account backup plan')
|
|
||||||
)
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
ordering = ('-date_start',)
|
|
||||||
verbose_name = _('Account backup execution')
|
|
||||||
|
|
||||||
@property
|
|
||||||
def types(self):
|
|
||||||
types = self.snapshot.get('types')
|
|
||||||
return types
|
|
||||||
|
|
||||||
@lazyproperty
|
|
||||||
def backup_accounts(self):
|
|
||||||
from accounts.models import Account
|
|
||||||
# TODO 可以优化一下查询 在账号上做 category 的缓存 避免数据量大时连表操作
|
|
||||||
qs = Account.objects.filter(
|
|
||||||
asset__platform__type__in=self.types
|
|
||||||
).annotate(type=F('asset__platform__type'))
|
|
||||||
return qs
|
|
||||||
|
|
||||||
@property
|
|
||||||
def manager_type(self):
|
|
||||||
return 'backup_account'
|
|
||||||
|
|
||||||
def start(self):
|
|
||||||
from accounts.automations.endpoint import ExecutionManager
|
|
||||||
manager = ExecutionManager(execution=self)
|
|
||||||
return manager.run()
|
|
||||||
|
|
|
@ -9,7 +9,7 @@ from terminal.models.component.storage import ReplayStorage
|
||||||
from users.models import User
|
from users.models import User
|
||||||
|
|
||||||
|
|
||||||
class AccountBackupExecutionTaskMsg(object):
|
class AccountBackupExecutionTaskMsg:
|
||||||
subject = _('Notification of account backup route task results')
|
subject = _('Notification of account backup route task results')
|
||||||
|
|
||||||
def __init__(self, name: str, user: User):
|
def __init__(self, name: str, user: User):
|
||||||
|
@ -34,7 +34,7 @@ class AccountBackupExecutionTaskMsg(object):
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class AccountBackupByObjStorageExecutionTaskMsg(object):
|
class AccountBackupByObjStorageExecutionTaskMsg:
|
||||||
subject = _('Notification of account backup route task results')
|
subject = _('Notification of account backup route task results')
|
||||||
|
|
||||||
def __init__(self, name: str, obj_storage: ReplayStorage):
|
def __init__(self, name: str, obj_storage: ReplayStorage):
|
||||||
|
@ -53,7 +53,7 @@ class AccountBackupByObjStorageExecutionTaskMsg(object):
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class ChangeSecretExecutionTaskMsg(object):
|
class ChangeSecretExecutionTaskMsg:
|
||||||
subject = _('Notification of implementation result of encryption change plan')
|
subject = _('Notification of implementation result of encryption change plan')
|
||||||
|
|
||||||
def __init__(self, name: str, user: User, summary):
|
def __init__(self, name: str, user: User, summary):
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
from .account import *
|
from .account import *
|
||||||
from .backup import *
|
|
||||||
from .base import *
|
from .base import *
|
||||||
|
from .service import *
|
||||||
from .template import *
|
from .template import *
|
||||||
from .virtual import *
|
from .virtual import *
|
||||||
from .service import *
|
|
||||||
|
|
|
@ -1,56 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
#
|
|
||||||
from django.utils.translation import gettext_lazy as _
|
|
||||||
from rest_framework import serializers
|
|
||||||
|
|
||||||
from accounts.models import AccountBackupAutomation, AccountBackupExecution
|
|
||||||
from common.const.choices import Trigger
|
|
||||||
from common.serializers.fields import LabeledChoiceField, EncryptedField
|
|
||||||
from common.utils import get_logger
|
|
||||||
from ops.mixin import PeriodTaskSerializerMixin
|
|
||||||
from orgs.mixins.serializers import BulkOrgResourceModelSerializer
|
|
||||||
|
|
||||||
logger = get_logger(__file__)
|
|
||||||
|
|
||||||
__all__ = ['AccountBackupSerializer', 'AccountBackupPlanExecutionSerializer']
|
|
||||||
|
|
||||||
|
|
||||||
class AccountBackupSerializer(PeriodTaskSerializerMixin, BulkOrgResourceModelSerializer):
|
|
||||||
zip_encrypt_password = EncryptedField(
|
|
||||||
label=_('Zip Encrypt Password'), required=False, max_length=40960, allow_blank=True,
|
|
||||||
allow_null=True, write_only=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
model = AccountBackupAutomation
|
|
||||||
read_only_fields = [
|
|
||||||
'date_created', 'date_updated', 'created_by',
|
|
||||||
'periodic_display', 'executed_amount'
|
|
||||||
]
|
|
||||||
fields = read_only_fields + [
|
|
||||||
'id', 'name', 'is_periodic', 'interval', 'crontab',
|
|
||||||
'comment', 'types', 'recipients_part_one', 'recipients_part_two', 'backup_type',
|
|
||||||
'is_password_divided_by_email', 'is_password_divided_by_obj_storage', 'obj_recipients_part_one',
|
|
||||||
'obj_recipients_part_two', 'zip_encrypt_password'
|
|
||||||
]
|
|
||||||
extra_kwargs = {
|
|
||||||
'name': {'required': True},
|
|
||||||
'executed_amount': {'label': _('Executions')},
|
|
||||||
'recipients': {
|
|
||||||
'label': _('Recipient'),
|
|
||||||
'help_text': _('Currently only mail sending is supported')
|
|
||||||
},
|
|
||||||
'types': {'label': _('Asset type')}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class AccountBackupPlanExecutionSerializer(serializers.ModelSerializer):
|
|
||||||
trigger = LabeledChoiceField(choices=Trigger.choices, label=_("Trigger mode"), read_only=True)
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
model = AccountBackupExecution
|
|
||||||
read_only_fields = [
|
|
||||||
'id', 'date_start', 'timedelta', 'snapshot',
|
|
||||||
'trigger', 'reason', 'is_success', 'org_id'
|
|
||||||
]
|
|
||||||
fields = read_only_fields + ['plan']
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
from .backup import *
|
||||||
from .base import *
|
from .base import *
|
||||||
from .change_secret import *
|
from .change_secret import *
|
||||||
from .check_account import *
|
from .check_account import *
|
||||||
|
|
|
@ -0,0 +1,54 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
#
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
from rest_framework import serializers
|
||||||
|
|
||||||
|
from accounts.models import BackupAccountAutomation
|
||||||
|
from common.const.choices import Trigger
|
||||||
|
from common.serializers.fields import LabeledChoiceField, EncryptedField
|
||||||
|
from common.utils import get_logger
|
||||||
|
from .base import BaseAutomationSerializer
|
||||||
|
|
||||||
|
logger = get_logger(__file__)
|
||||||
|
|
||||||
|
__all__ = ['BackupAccountSerializer', 'BackupAccountExecutionSerializer']
|
||||||
|
|
||||||
|
|
||||||
|
class BackupAccountSerializer(BaseAutomationSerializer):
|
||||||
|
zip_encrypt_password = EncryptedField(
|
||||||
|
label=_('Zip Encrypt Password'), required=False, max_length=40960, allow_blank=True,
|
||||||
|
allow_null=True, write_only=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = BackupAccountAutomation
|
||||||
|
read_only_fields = BaseAutomationSerializer.Meta.read_only_fields
|
||||||
|
fields = BaseAutomationSerializer.Meta.fields + read_only_fields + [
|
||||||
|
'types', 'recipients_part_one', 'recipients_part_two', 'backup_type',
|
||||||
|
'is_password_divided_by_email', 'is_password_divided_by_obj_storage',
|
||||||
|
'obj_recipients_part_one', 'obj_recipients_part_two', 'zip_encrypt_password'
|
||||||
|
]
|
||||||
|
extra_kwargs = {
|
||||||
|
'name': {'required': True},
|
||||||
|
'obj_recipients_part_one': {
|
||||||
|
'label': _('Recipient part one'), 'help_text': _(
|
||||||
|
"Currently only mail sending is supported"
|
||||||
|
)},
|
||||||
|
'obj_recipients_part_two': {
|
||||||
|
'label': _('Recipient part two'), 'help_text': _(
|
||||||
|
"Currently only mail sending is supported"
|
||||||
|
)},
|
||||||
|
'types': {'label': _('Asset type')}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class BackupAccountExecutionSerializer(serializers.ModelSerializer):
|
||||||
|
trigger = LabeledChoiceField(choices=Trigger.choices, label=_("Trigger mode"), read_only=True)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = BackupAccountAutomation
|
||||||
|
read_only_fields = [
|
||||||
|
'id', 'date_start', 'timedelta', 'snapshot',
|
||||||
|
'trigger', 'reason', 'is_success', 'org_id'
|
||||||
|
]
|
||||||
|
fields = read_only_fields + ['plan']
|
|
@ -1,5 +1,4 @@
|
||||||
from .automation import *
|
from .automation import *
|
||||||
from .backup_account import *
|
|
||||||
from .gather_accounts import *
|
from .gather_accounts import *
|
||||||
from .push_account import *
|
from .push_account import *
|
||||||
from .remove_account import *
|
from .remove_account import *
|
||||||
|
|
|
@ -1,42 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
#
|
|
||||||
from celery import shared_task
|
|
||||||
from django.utils.translation import gettext_lazy as _
|
|
||||||
|
|
||||||
from common.utils import get_object_or_none, get_logger
|
|
||||||
from orgs.utils import tmp_to_org, tmp_to_root_org
|
|
||||||
|
|
||||||
logger = get_logger(__file__)
|
|
||||||
|
|
||||||
|
|
||||||
def task_activity_callback(self, pid, trigger, *args, **kwargs):
|
|
||||||
from accounts.models import AccountBackupAutomation
|
|
||||||
with tmp_to_root_org():
|
|
||||||
plan = get_object_or_none(AccountBackupAutomation, pk=pid)
|
|
||||||
if not plan:
|
|
||||||
return
|
|
||||||
if not plan.latest_execution:
|
|
||||||
return
|
|
||||||
resource_ids = plan.latest_execution.backup_accounts
|
|
||||||
org_id = plan.org_id
|
|
||||||
return resource_ids, org_id
|
|
||||||
|
|
||||||
|
|
||||||
@shared_task(
|
|
||||||
verbose_name=_('Execute account backup plan'),
|
|
||||||
activity_callback=task_activity_callback,
|
|
||||||
description=_(
|
|
||||||
"""
|
|
||||||
When performing scheduled or manual account backups, this task is used
|
|
||||||
"""
|
|
||||||
)
|
|
||||||
)
|
|
||||||
def execute_account_backup_task(pid, trigger, **kwargs):
|
|
||||||
from accounts.models import AccountBackupAutomation
|
|
||||||
with tmp_to_root_org():
|
|
||||||
plan = get_object_or_none(AccountBackupAutomation, pk=pid)
|
|
||||||
if not plan:
|
|
||||||
logger.error("No account backup route plan found: {}".format(pid))
|
|
||||||
return
|
|
||||||
with tmp_to_org(plan.org):
|
|
||||||
plan.execute(trigger)
|
|
|
@ -16,7 +16,7 @@ router.register(r'account-secrets', api.AccountSecretsViewSet, 'account-secret')
|
||||||
router.register(r'account-templates', api.AccountTemplateViewSet, 'account-template')
|
router.register(r'account-templates', api.AccountTemplateViewSet, 'account-template')
|
||||||
router.register(r'account-template-secrets', api.AccountTemplateSecretsViewSet, 'account-template-secret')
|
router.register(r'account-template-secrets', api.AccountTemplateSecretsViewSet, 'account-template-secret')
|
||||||
router.register(r'account-backup-plans', api.AccountBackupPlanViewSet, 'account-backup')
|
router.register(r'account-backup-plans', api.AccountBackupPlanViewSet, 'account-backup')
|
||||||
router.register(r'account-backup-plan-executions', api.AccountBackupPlanExecutionViewSet, 'account-backup-execution')
|
router.register(r'account-backup-plan-executions', api.BackupAccountExecutionViewSet, 'account-backup-execution')
|
||||||
router.register(r'change-secret-automations', api.ChangeSecretAutomationViewSet, 'change-secret-automation')
|
router.register(r'change-secret-automations', api.ChangeSecretAutomationViewSet, 'change-secret-automation')
|
||||||
router.register(r'change-secret-executions', api.ChangSecretExecutionViewSet, 'change-secret-execution')
|
router.register(r'change-secret-executions', api.ChangSecretExecutionViewSet, 'change-secret-execution')
|
||||||
router.register(r'change-secret-records', api.ChangeSecretRecordViewSet, 'change-secret-record')
|
router.register(r'change-secret-records', api.ChangeSecretRecordViewSet, 'change-secret-record')
|
||||||
|
|
Loading…
Reference in New Issue