Merge branch 'pam' of github.com:jumpserver/jumpserver into pam

pull/14586/head
ibuler 2024-12-04 18:33:31 +08:00
commit aa52ab13ce
22 changed files with 361 additions and 325 deletions

View File

@ -1,41 +1,36 @@
# -*- coding: utf-8 -*-
#
from rest_framework import status, viewsets
from rest_framework.response import Response
from accounts import serializers
from accounts.const import AutomationTypes
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 .base import AutomationExecutionViewSet
__all__ = [
'AccountBackupPlanViewSet', 'AccountBackupPlanExecutionViewSet'
'BackupAccountViewSet', 'BackupAccountExecutionViewSet'
]
class AccountBackupPlanViewSet(OrgBulkModelViewSet):
model = AccountBackupAutomation
class BackupAccountViewSet(OrgBulkModelViewSet):
model = BackupAccountAutomation
filterset_fields = ('name',)
search_fields = filterset_fields
serializer_class = serializers.AccountBackupSerializer
serializer_class = serializers.BackupAccountSerializer
class AccountBackupPlanExecutionViewSet(viewsets.ModelViewSet):
serializer_class = serializers.AccountBackupPlanExecutionSerializer
search_fields = ('trigger', 'plan__name')
filterset_fields = ('trigger', 'plan_id', 'plan__name')
http_method_names = ['get', 'post', 'options']
class BackupAccountExecutionViewSet(AutomationExecutionViewSet):
rbac_perms = (
("list", "accounts.view_backupaccountexecution"),
("retrieve", "accounts.view_backupaccountexecution"),
("create", "accounts.add_backupaccountexecution"),
("report", "accounts.view_backupaccountexecution"),
)
tp = AutomationTypes.backup_account
def get_queryset(self):
queryset = AccountBackupExecution.objects.all()
queryset = super().get_queryset()
queryset = queryset.filter(automation__type=self.tp)
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)

View File

@ -1,6 +1,5 @@
# -*- coding: utf-8 -*-
#
from django.db import transaction
from django.http import HttpResponse
from django.shortcuts import get_object_or_404
from rest_framework import status
@ -13,7 +12,6 @@ from accounts.filters import GatheredAccountFilterSet
from accounts.models import GatherAccountsAutomation, AutomationExecution
from accounts.models import GatheredAccount
from assets.models import Asset
from accounts.tasks.common import quickstart_automation_by_snapshot
from orgs.mixins.api import OrgBulkModelViewSet
from .base import AutomationExecutionViewSet

View File

@ -3,15 +3,17 @@ import time
from collections import defaultdict, OrderedDict
from django.conf import settings
from django.db.models import F
from django.utils.translation import gettext_lazy as _
from rest_framework import serializers
from xlsxwriter import Workbook
from accounts.const import AccountBackupType
from accounts.models.automations.backup_account import AccountBackupAutomation
from accounts.models import BackupAccountAutomation, Account
from accounts.notifications import AccountBackupExecutionTaskMsg, AccountBackupByObjStorageExecutionTaskMsg
from accounts.serializers import AccountSecretSerializer
from assets.const import AllTypes
from common.const import Status
from common.utils.file import encrypt_and_compress_zip_file, zip_files
from common.utils.timezone import local_now_filename, local_now_display
from terminal.models.component.storage import ReplayStorage
@ -20,6 +22,7 @@ from users.models import User
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')
class RecipientsNotFound(Exception):
pass
@ -73,9 +76,9 @@ class BaseAccountHandler:
class AssetAccountHandler(BaseAccountHandler):
@staticmethod
def get_filename(plan_name):
def get_filename(name):
filename = os.path.join(
PATH, f'{plan_name}-{local_now_filename()}-{time.time()}.xlsx'
PATH, f'{name}-{local_now_filename()}-{time.time()}.xlsx'
)
return filename
@ -117,32 +120,41 @@ class AssetAccountHandler(BaseAccountHandler):
cls.handler_secret(data, section)
data_map.update(cls.add_rows(data, header_fields, sheet_name))
number_of_backup_accounts = _('Number of backup accounts')
print('\n\033[33m- {}: {}\033[0m'.format(number_of_backup_accounts, accounts.count()))
print('\033[33m- {}: {}\033[0m'.format(number_of_backup_accounts, accounts.count()))
return data_map
class AccountBackupHandler:
def __init__(self, execution):
def __init__(self, manager, execution):
self.manager = manager
self.execution = execution
self.plan_name = self.execution.plan.name
self.is_frozen = False # 任务状态冻结标志
self.name = self.execution.snapshot.get('name', '-')
def get_accounts(self):
# TODO 可以优化一下查询 在账号上做 category 的缓存 避免数据量大时连表操作
types = self.execution.snapshot.get('types', [])
self.manager.summary['total_types'] = len(types)
qs = Account.objects.filter(
asset__platform__type__in=types
).annotate(type=F('asset__platform__type'))
return qs
def create_excel(self, section='complete'):
hint = _('Generating asset or application related backup information files')
hint = _('Generating asset related backup information files')
print(
'\n'
f'\033[32m>>> {hint}\033[0m'
''
)
# Print task start date
time_start = time.time()
files = []
accounts = self.execution.backup_accounts
accounts = self.get_accounts()
self.manager.summary['total_accounts'] = accounts.count()
data_map = AssetAccountHandler.create_data_map(accounts, section)
if not data_map:
return files
filename = AssetAccountHandler.get_filename(self.plan_name)
filename = AssetAccountHandler.get_filename(self.name)
wb = Workbook(filename)
for sheet, data in data_map.items():
@ -163,21 +175,19 @@ class AccountBackupHandler:
return
recipients = User.objects.filter(id__in=list(recipients))
print(
'\n'
f'\033[32m>>> {_("Start sending backup emails")}\033[0m'
''
)
plan_name = self.plan_name
name = self.name
for user in recipients:
if not user.secret_key:
attachment_list = []
else:
attachment = os.path.join(PATH, f'{plan_name}-{local_now_filename()}-{time.time()}.zip')
attachment = os.path.join(PATH, f'{name}-{local_now_filename()}-{time.time()}.zip')
encrypt_and_compress_zip_file(attachment, user.secret_key, files)
attachment_list = [attachment, ]
AccountBackupExecutionTaskMsg(plan_name, user).publish(attachment_list)
email_sent_to = _('Email sent to')
print('{} {}({})'.format(email_sent_to, user, user.email))
attachment_list = [attachment]
AccountBackupExecutionTaskMsg(name, user).publish(attachment_list)
for file in files:
os.remove(file)
@ -186,63 +196,41 @@ class AccountBackupHandler:
return
recipients = ReplayStorage.objects.filter(id__in=list(recipients))
print(
'\n'
'\033[32m>>> 📃 ---> sftp \033[0m'
''
)
plan_name = self.plan_name
name = self.name
encrypt_file = _('Encrypting files using encryption password')
for rec in recipients:
attachment = os.path.join(PATH, f'{plan_name}-{local_now_filename()}-{time.time()}.zip')
attachment = os.path.join(PATH, f'{name}-{local_now_filename()}-{time.time()}.zip')
if password:
print(f'\033[32m>>> {encrypt_file}\033[0m')
encrypt_and_compress_zip_file(attachment, password, files)
else:
zip_files(attachment, files)
attachment_list = attachment
AccountBackupByObjStorageExecutionTaskMsg(plan_name, rec).publish(attachment_list)
AccountBackupByObjStorageExecutionTaskMsg(name, rec).publish(attachment_list)
file_sent_to = _('The backup file will be sent to')
print('{}: {}({})'.format(file_sent_to, rec.name, rec.id))
for file in files:
os.remove(file)
def step_perform_task_update(self, is_success, reason):
self.execution.reason = reason[:1024]
self.execution.is_success = is_success
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):
is_success = False
error = '-'
try:
backup_type = self.execution.snapshot.get('backup_type', AccountBackupType.email.value)
if backup_type == AccountBackupType.email.value:
backup_type = self.execution.snapshot.get('backup_type', AccountBackupType.email)
if backup_type == AccountBackupType.email:
self.backup_by_email()
elif backup_type == AccountBackupType.object_storage.value:
elif backup_type == AccountBackupType.object_storage:
self.backup_by_obj_storage()
except Exception as e:
self.is_frozen = True
print(e)
error = str(e)
else:
is_success = True
finally:
reason = error
self.step_perform_task_update(is_success, reason)
self.step_finished(is_success)
print(f'\033[31m>>> {error}\033[0m')
self.execution.status = Status.error
self.execution.summary['error'] = error
def backup_by_obj_storage(self):
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_two = self.execution.snapshot.get('obj_recipients_part_two', [])
no_assigned_sftp_server = _('The backup task has no assigned sftp server')
@ -266,7 +254,6 @@ class AccountBackupHandler:
self.send_backup_obj_storage(files, recipients, zip_encrypt_password)
def backup_by_email(self):
warn_text = _('The backup task has no assigned recipient')
recipients_part_one = self.execution.snapshot.get('recipients_part_one', [])
recipients_part_two = self.execution.snapshot.get('recipients_part_two', [])
@ -276,7 +263,7 @@ class AccountBackupHandler:
f'\033[31m>>> {warn_text}\033[0m'
''
)
raise RecipientsNotFound('Not Found Recipients')
return
if recipients_part_one and recipients_part_two:
print(f'\033[32m>>> {split_help_text}\033[0m')
files = self.create_excel(section='front')
@ -290,18 +277,5 @@ class AccountBackupHandler:
self.send_backup_mail(files, recipients)
def run(self):
plan_start = _('Plan start')
plan_end = _('Plan end')
time_cost = _('Duration')
error = _('An exception occurred during task execution')
print('{}: {}'.format(plan_start, local_now_display()))
time_start = time.time()
try:
self._run()
except Exception as e:
print(error)
print(e)
finally:
print('\n{}: {}'.format(plan_end, local_now_display()))
timedelta = round((time.time() - time_start), 2)
print('{}: {}s'.format(time_cost, timedelta))
print('{}: {}'.format(_('Plan start'), local_now_display()))
self._run()

View File

@ -1,11 +1,9 @@
# -*- coding: utf-8 -*-
#
import time
from django.utils.translation import gettext_lazy as _
from assets.automations.base.manager import BaseManager
from common.db.utils import safe_db_connection
from common.utils.timezone import local_now_display
from .handlers import AccountBackupHandler
@ -14,23 +12,19 @@ class AccountBackupManager(BaseManager):
def do_run(self):
execution = self.execution
account_backup_execution_being_executed = _('The account backup plan is being executed')
print(f'\n\033[33m# {account_backup_execution_being_executed}\033[0m')
handler = AccountBackupHandler(execution)
print(f'\033[33m# {account_backup_execution_being_executed}\033[0m')
handler = AccountBackupHandler(self, execution)
handler.run()
def send_report_if_need(self):
pass
def update_execution(self):
timedelta = int(time.time() - self.time_start)
self.execution.timedelta = timedelta
with safe_db_connection():
self.execution.save(update_fields=['timedelta', ])
def print_summary(self):
print('\n\n' + '-' * 80)
plan_execution_end = _('Plan execution end')
print('{} {}\n'.format(plan_execution_end, local_now_display()))
time_cost = _('Duration')
print('{}: {}s'.format(time_cost, self.duration))
def get_report_template(self):
return "accounts/backup_account_report.html"

View File

@ -18,7 +18,7 @@ class ExecutionManager:
AutomationTypes.gather_accounts: GatherAccountsManager,
AutomationTypes.verify_gateway_account: VerifyGatewayAccountManager,
AutomationTypes.check_account: CheckAccountManager,
'backup_account': AccountBackupManager,
AutomationTypes.backup_account: AccountBackupManager,
}
def __init__(self, execution):

View File

@ -28,13 +28,14 @@ class AutomationTypes(models.TextChoices):
gather_accounts = 'gather_accounts', _('Gather accounts')
verify_gateway_account = 'verify_gateway_account', _('Verify gateway account')
check_account = 'check_account', _('Check account')
backup_account = 'backup_account', _('Backup account')
@classmethod
def get_type_model(cls, tp):
from accounts.models import (
PushAccountAutomation, ChangeSecretAutomation,
VerifyAccountAutomation, GatherAccountsAutomation,
CheckAccountAutomation,
CheckAccountAutomation, BackupAccountAutomation
)
type_model_dict = {
cls.push_account: PushAccountAutomation,
@ -42,6 +43,7 @@ class AutomationTypes(models.TextChoices):
cls.verify_account: VerifyAccountAutomation,
cls.gather_accounts: GatherAccountsAutomation,
cls.check_account: CheckAccountAutomation,
cls.backup_account: BackupAccountAutomation,
}
return type_model_dict.get(tp)

View File

@ -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',
),
]

View File

@ -0,0 +1,17 @@
# Generated by Django 4.1.13 on 2024-12-04 08:36
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('accounts', '0019_backupaccountautomation_and_more'),
]
operations = [
migrations.AlterModelOptions(
name='automationexecution',
options={'permissions': [('view_changesecretexecution', 'Can view change secret execution'), ('add_changesecretexecution', 'Can add change secret execution'), ('view_gatheraccountsexecution', 'Can view gather accounts execution'), ('add_gatheraccountsexecution', 'Can add gather accounts execution'), ('view_pushaccountexecution', 'Can view push account execution'), ('add_pushaccountexecution', 'Can add push account execution'), ('view_backupaccountexecution', 'Can view backup account execution'), ('add_backupaccountexecution', 'Can add backup account execution')], 'verbose_name': 'Automation execution', 'verbose_name_plural': 'Automation executions'},
),
]

View File

@ -1,30 +1,26 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
import uuid
from celery import current_task
from django.db import models
from django.db.models import F
from django.utils.translation import gettext_lazy as _
from accounts.const import AccountBackupType
from common.const.choices import Trigger
from accounts.const import AccountBackupType, AutomationTypes
from common.db import fields
from common.db.encoder import ModelJSONFieldEncoder
from common.utils import get_logger, lazyproperty
from ops.mixin import PeriodTaskModelMixin
from orgs.mixins.models import OrgModelMixin, JMSOrgBaseModel
from common.utils import get_logger
from .base import AccountBaseAutomation
__all__ = ['AccountBackupAutomation', 'AccountBackupExecution']
__all__ = ['BackupAccountAutomation']
logger = get_logger(__file__)
class AccountBackupAutomation(PeriodTaskModelMixin, JMSOrgBaseModel):
class BackupAccountAutomation(AccountBaseAutomation):
types = models.JSONField(default=list)
backup_type = models.CharField(max_length=128, choices=AccountBackupType.choices,
default=AccountBackupType.email.value, verbose_name=_('Backup type'))
backup_type = models.CharField(
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_obj_storage = models.BooleanField(default=True, verbose_name=_('Password divided'))
recipients_part_one = models.ManyToManyField(
@ -51,27 +47,11 @@ class AccountBackupAutomation(PeriodTaskModelMixin, JMSOrgBaseModel):
return f'{self.name}({self.org_id})'
class Meta:
ordering = ['name']
unique_together = [('name', 'org_id')]
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):
return {
'id': self.id,
'name': self.name,
'is_periodic': self.is_periodic,
'interval': self.interval,
'crontab': self.crontab,
'org_id': self.org_id,
'created_by': self.created_by,
attr_json = super().to_attr_json()
attr_json.update({
'types': self.types,
'backup_type': self.backup_type,
'is_password_divided_by_email': self.is_password_divided_by_email,
@ -93,75 +73,9 @@ class AccountBackupAutomation(PeriodTaskModelMixin, JMSOrgBaseModel):
str(obj_storage.id): (str(obj_storage.name), str(obj_storage.type))
for obj_storage in self.obj_recipients_part_two.all()
},
}
})
return attr_json
@property
def executed_amount(self):
return self.execution.count()
def execute(self, trigger):
try:
hid = current_task.request.id
except AttributeError:
hid = str(uuid.uuid4())
execution = AccountBackupExecution.objects.create(
id=hid, plan=self, snapshot=self.to_attr_json(), trigger=trigger
)
return execution.start()
@lazyproperty
def latest_execution(self):
return self.execution.first()
class AccountBackupExecution(OrgModelMixin):
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
date_start = models.DateTimeField(
auto_now_add=True, verbose_name=_('Date start')
)
timedelta = models.FloatField(
default=0.0, verbose_name=_('Time'), null=True
)
snapshot = models.JSONField(
encoder=ModelJSONFieldEncoder, default=dict,
blank=True, null=True, verbose_name=_('Account backup snapshot')
)
trigger = models.CharField(
max_length=128, default=Trigger.manual, choices=Trigger.choices,
verbose_name=_('Trigger mode')
)
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()
def save(self, *args, **kwargs):
self.type = AutomationTypes.backup_account
super().save(*args, **kwargs)

View File

@ -40,6 +40,9 @@ class AutomationExecution(AssetAutomationExecution):
('view_pushaccountexecution', _('Can view push account execution')),
('add_pushaccountexecution', _('Can add push account execution')),
('view_backupaccountexecution', _('Can view backup account execution')),
('add_backupaccountexecution', _('Can add backup account execution')),
]
@property

View File

@ -9,7 +9,7 @@ from terminal.models.component.storage import ReplayStorage
from users.models import User
class AccountBackupExecutionTaskMsg(object):
class AccountBackupExecutionTaskMsg:
subject = _('Notification of account backup route task results')
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')
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')
def __init__(self, name: str, user: User, summary):

View File

@ -1,6 +1,5 @@
from .account import *
from .backup import *
from .base import *
from .service import *
from .template import *
from .virtual import *
from .service import *

View File

@ -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']

View File

@ -1,3 +1,4 @@
from .backup import *
from .base import *
from .change_secret import *
from .check_account import *

View File

@ -0,0 +1,45 @@
# -*- coding: utf-8 -*-
#
from django.utils.translation import gettext_lazy as _
from accounts.const import AutomationTypes
from accounts.models import BackupAccountAutomation
from common.serializers.fields import EncryptedField
from common.utils import get_logger
from .base import BaseAutomationSerializer
logger = get_logger(__file__)
__all__ = ['BackupAccountSerializer']
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')}
}
@property
def model_type(self):
return AutomationTypes.backup_account

View File

@ -26,15 +26,14 @@ class BaseAutomationSerializer(PeriodTaskSerializerMixin, BulkOrgResourceModelSe
class Meta:
read_only_fields = [
'date_created', 'date_updated', 'created_by',
'periodic_display', 'executed_amount'
'periodic_display', 'executed_amount', 'type'
]
fields = read_only_fields + [
'id', 'name', 'is_periodic', 'interval', 'crontab', 'comment',
'type', 'accounts', 'nodes', 'assets', 'is_active',
'accounts', 'nodes', 'assets', 'is_active',
]
extra_kwargs = {
'name': {'required': True},
'type': {'read_only': True},
'executed_amount': {'label': _('Executions')},
}
@ -58,7 +57,7 @@ class AutomationExecutionSerializer(serializers.ModelSerializer):
class Meta:
model = AutomationExecution
read_only_fields = [
'trigger', 'date_start', 'date_finished', 'snapshot', 'status'
'trigger', 'date_start', 'date_finished', 'snapshot', 'status', 'duration'
]
fields = ['id', 'automation', 'type'] + read_only_fields

View File

@ -1,5 +1,4 @@
from .automation import *
from .backup_account import *
from .gather_accounts import *
from .push_account import *
from .remove_account import *

View File

@ -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)

View File

@ -0,0 +1,78 @@
{% load i18n %}
<div class='summary'>
<p>{% trans 'The following is a summary of account backup tasks, please review and handle them' %}</p>
<table>
<caption></caption>
<thead>
<tr>
<th colspan='2'>任务汇总:</th>
</tr>
</thead>
<tbody>
<tr>
<td>{% trans 'Task name' %}:</td>
<td>{{ execution.automation.name }} </td>
</tr>
<tr>
<td>{% trans 'Date start' %}:</td>
<td>{{ execution.date_start | date:"Y/m/d H:i:s" }}</td>
</tr>
<tr>
<td>{% trans 'Date end' %}:</td>
<td>{{ execution.date_finished | date:"Y/m/d H:i:s" }}</td>
</tr>
<tr>
<td>{% trans 'Time using' %}:</td>
<td>{{ execution.duration }}s</td>
</tr>
<tr>
<td>{% trans 'Account count' %}:</td>
<td>{{ summary.total_accounts }}</td>
</tr>
<tr>
<td>{% trans 'Type count' %}:</td>
<td>{{ summary.total_types }}</td>
</tr>
</tbody>
</table>
</div>
<style>
table {
width: 100%;
border-collapse: collapse;
max-width: 100%;
text-align: left;
margin-top: 10px;
padding: 20px;
}
th {
background: #f2f2f2;
font-size: 14px;
padding: 5px;
border: 1px solid #ddd;
}
tr :first-child {
width: 30%;
}
td {
border: 1px solid #ddd;
padding: 5px;
font-size: 12px;
}
.result {
margin-top: 20px;
}
.result tr :first-child {
width: 10%;
}
</style>

View File

@ -14,8 +14,8 @@ router.register(r'gathered-accounts', api.GatheredAccountViewSet, 'gathered-acco
router.register(r'account-secrets', api.AccountSecretsViewSet, 'account-secret')
router.register(r'account-templates', api.AccountTemplateViewSet, 'account-template')
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-plan-executions', api.AccountBackupPlanExecutionViewSet, 'account-backup-execution')
router.register(r'account-backup-plans', api.BackupAccountViewSet, 'account-backup')
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-executions', api.ChangSecretExecutionViewSet, 'change-secret-execution')
router.register(r'change-secret-records', api.ChangeSecretRecordViewSet, 'change-secret-record')

View File

@ -98,7 +98,7 @@ class BaseManager:
self.summary = defaultdict(int)
self.result = defaultdict(list)
self.duration = 0
self.status = 'success'
self.status = Status.success
def get_assets_group_by_platform(self):
return self.execution.all_assets_group_by_platform()

View File

@ -42,17 +42,17 @@ class AutomationExecutionSerializer(serializers.ModelSerializer):
class Meta:
model = AutomationExecution
read_only_fields = [
'trigger', 'date_start', 'date_finished', 'snapshot', 'status'
'trigger', 'date_start', 'date_finished', 'snapshot', 'status', 'duration'
]
fields = ['id', 'automation'] + read_only_fields
@staticmethod
def get_status(obj):
if obj.status == 'success':
return _("Success")
elif obj.status == 'pending':
return _("Pending")
return obj.status
from common.const import Status
status = Status._member_map_.get(obj.status)
if status is None:
return obj.status
return status.label
@staticmethod
def get_snapshot(obj):