mirror of https://github.com/jumpserver/jumpserver
账号备份
parent
5358f35c08
commit
3e1c832964
|
@ -0,0 +1,59 @@
|
||||||
|
# Generated by Django 3.2.13 on 2022-08-29 11:46
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
from assets.const import Category
|
||||||
|
from assets.models import Type
|
||||||
|
|
||||||
|
|
||||||
|
def update_account_backup_type(apps, schema_editor):
|
||||||
|
backup_model = apps.get_model('assets', 'AccountBackupPlan')
|
||||||
|
all_number = 4294967295
|
||||||
|
asset_number = Type.choices_to_value([Category.HOST])
|
||||||
|
app_number = Type.choices_to_value([
|
||||||
|
Category.NETWORKING, Category.DATABASE, Category.CLOUD, Category.WEB]
|
||||||
|
)
|
||||||
|
|
||||||
|
backup_model.objects.filter(types=255).update(types=all_number)
|
||||||
|
backup_model.objects.filter(types=1).update(types=asset_number)
|
||||||
|
backup_model.objects.filter(types=2).update(types=app_number)
|
||||||
|
|
||||||
|
backup_execution_model = apps.get_model('assets', 'AccountBackupPlanExecution')
|
||||||
|
choices_dict = {
|
||||||
|
'all': Type.get_types(value=all_number),
|
||||||
|
'asset': Type.get_types(value=asset_number),
|
||||||
|
'app': Type.get_types(value=app_number)
|
||||||
|
}
|
||||||
|
qs_dict = {
|
||||||
|
'all': backup_execution_model.objects.filter(plan__types=255),
|
||||||
|
'asset': backup_execution_model.objects.filter(plan__types=1),
|
||||||
|
'app': backup_execution_model.objects.filter(plan__types=2)
|
||||||
|
}
|
||||||
|
|
||||||
|
backup_executions = []
|
||||||
|
for k, qs in qs_dict.items():
|
||||||
|
type_choices = choices_dict[k]
|
||||||
|
for i in qs:
|
||||||
|
i.plan_snapshot['types'] = type_choices
|
||||||
|
backup_executions.append(i)
|
||||||
|
backup_execution_model.objects.bulk_update(backup_executions, fields=['plan_snapshot'])
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
dependencies = [
|
||||||
|
('assets', '0106_auto_20220819_1523'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='accountbackupplan',
|
||||||
|
name='types',
|
||||||
|
field=models.BigIntegerField(
|
||||||
|
choices=[(4294967295, 'All'), (1, 'Linux'), (2, 'Windows'), (4, 'Unix'), (8, 'BSD'), (16, 'MacOS'),
|
||||||
|
(32, 'Mainframe'), (64, 'Other host'), (127, 'Host'), (128, 'Switch'), (256, 'Router'),
|
||||||
|
(512, 'Firewall'), (1024, 'Other device'), (1920, 'NetworkDevice'), (2048, 'MySQL'),
|
||||||
|
(4096, 'MariaDB'), (8192, 'PostgreSQL'), (16384, 'Oracle'), (32768, 'SQLServer'),
|
||||||
|
(65536, 'MongoDB'), (131072, 'Redis'), (260096, 'Database'), (262144, 'Clouding'),
|
||||||
|
(524288, 'Web')], default=4294967295, verbose_name='Type'),
|
||||||
|
),
|
||||||
|
migrations.RunPython(update_account_backup_type),
|
||||||
|
]
|
|
@ -2,6 +2,7 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
#
|
#
|
||||||
import uuid
|
import uuid
|
||||||
|
from functools import reduce
|
||||||
|
|
||||||
from celery import current_task
|
from celery import current_task
|
||||||
from django.db import models
|
from django.db import models
|
||||||
|
@ -13,40 +14,63 @@ from common.utils import get_logger
|
||||||
from common.db.encoder import ModelJSONFieldEncoder
|
from common.db.encoder import ModelJSONFieldEncoder
|
||||||
from common.db.models import BitOperationChoice
|
from common.db.models import BitOperationChoice
|
||||||
from common.mixins.models import CommonModelMixin
|
from common.mixins.models import CommonModelMixin
|
||||||
|
from ..const import AllTypes, Category
|
||||||
|
|
||||||
__all__ = ['AccountBackupPlan', 'AccountBackupPlanExecution', 'Type']
|
__all__ = ['AccountBackupPlan', 'AccountBackupPlanExecution', 'Type']
|
||||||
|
|
||||||
logger = get_logger(__file__)
|
logger = get_logger(__file__)
|
||||||
|
|
||||||
|
|
||||||
|
def _choice_map(default=None):
|
||||||
|
offset = 0
|
||||||
|
temp_key = 0b1
|
||||||
|
|
||||||
|
if default is None:
|
||||||
|
_all = (0b1 << 32) - 1
|
||||||
|
else:
|
||||||
|
_all = default
|
||||||
|
|
||||||
|
choices = {
|
||||||
|
_all: ('all', 'All')
|
||||||
|
}
|
||||||
|
|
||||||
|
for info in AllTypes.grouped_choices_to_objs():
|
||||||
|
temp_keys = []
|
||||||
|
for c in info['children']:
|
||||||
|
key = temp_key << offset
|
||||||
|
temp_keys.append(key)
|
||||||
|
choices[key] = (c['value'], c['display_name'])
|
||||||
|
offset += 1
|
||||||
|
parent_key = reduce(lambda x, y: x | y, temp_keys)
|
||||||
|
choices[parent_key] = (info['value'], info['display_name'])
|
||||||
|
return choices
|
||||||
|
|
||||||
|
|
||||||
class Type(BitOperationChoice):
|
class Type(BitOperationChoice):
|
||||||
NONE = 0
|
NONE = 0
|
||||||
ALL = 0xff
|
|
||||||
|
|
||||||
Asset = 0b1
|
ALL = (0b1 << 32) - 1
|
||||||
App = 0b1 << 1
|
TYPE_MAP = _choice_map(ALL)
|
||||||
|
|
||||||
DB_CHOICES = (
|
DB_CHOICES = tuple((k, v[1]) for k, v in TYPE_MAP.items())
|
||||||
(ALL, _('All')),
|
|
||||||
(Asset, _('Asset')),
|
|
||||||
(App, _('Application'))
|
|
||||||
)
|
|
||||||
|
|
||||||
NAME_MAP = {
|
NAME_MAP = {k: v[0] for k, v in TYPE_MAP.items()}
|
||||||
ALL: "all",
|
|
||||||
Asset: "asset",
|
|
||||||
App: "application"
|
|
||||||
}
|
|
||||||
|
|
||||||
NAME_MAP_REVERSE = {v: k for k, v in NAME_MAP.items()}
|
NAME_MAP_REVERSE = {v: k for k, v in NAME_MAP.items()}
|
||||||
CHOICES = []
|
CHOICES = []
|
||||||
for i, j in DB_CHOICES:
|
for i, j in DB_CHOICES:
|
||||||
CHOICES.append((NAME_MAP[i], j))
|
CHOICES.append((NAME_MAP[i], j))
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_types(cls, value: int) -> list:
|
||||||
|
exclude_types = ['all'] + Category.values
|
||||||
|
current_all = cls.value_to_choices(value)
|
||||||
|
return list(filter(lambda x: x not in exclude_types, current_all))
|
||||||
|
|
||||||
|
|
||||||
class AccountBackupPlan(CommonModelMixin, PeriodTaskModelMixin, OrgModelMixin):
|
class AccountBackupPlan(CommonModelMixin, PeriodTaskModelMixin, OrgModelMixin):
|
||||||
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
|
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
|
||||||
types = models.IntegerField(choices=Type.DB_CHOICES, default=Type.ALL, verbose_name=_('Type'))
|
types = models.BigIntegerField(choices=Type.DB_CHOICES, default=Type.ALL, verbose_name=_('Type'))
|
||||||
recipients = models.ManyToManyField(
|
recipients = models.ManyToManyField(
|
||||||
'users.User', related_name='recipient_escape_route_plans', blank=True,
|
'users.User', related_name='recipient_escape_route_plans', blank=True,
|
||||||
verbose_name=_("Recipient")
|
verbose_name=_("Recipient")
|
||||||
|
@ -77,7 +101,7 @@ class AccountBackupPlan(CommonModelMixin, PeriodTaskModelMixin, OrgModelMixin):
|
||||||
'crontab': self.crontab,
|
'crontab': self.crontab,
|
||||||
'org_id': self.org_id,
|
'org_id': self.org_id,
|
||||||
'created_by': self.created_by,
|
'created_by': self.created_by,
|
||||||
'types': Type.value_to_choices(self.types),
|
'types': Type.get_types(self.types),
|
||||||
'recipients': {
|
'recipients': {
|
||||||
str(recipient.id): (str(recipient), bool(recipient.secret_key))
|
str(recipient.id): (str(recipient), bool(recipient.secret_key))
|
||||||
for recipient in self.recipients.all()
|
for recipient in self.recipients.all()
|
||||||
|
|
|
@ -31,7 +31,6 @@ class AccountTemplateSerializer(AuthSerializerMixin, BulkOrgResourceModelSeriali
|
||||||
for k, v in cls().fields.items():
|
for k, v in cls().fields.items():
|
||||||
if v.required and k not in attrs:
|
if v.required and k not in attrs:
|
||||||
required_field_dict[k] = error
|
required_field_dict[k] = error
|
||||||
print(required_field_dict)
|
|
||||||
if not required_field_dict:
|
if not required_field_dict:
|
||||||
return
|
return
|
||||||
raise serializers.ValidationError(required_field_dict)
|
raise serializers.ValidationError(required_field_dict)
|
||||||
|
|
|
@ -4,10 +4,10 @@ from openpyxl import Workbook
|
||||||
from collections import defaultdict, OrderedDict
|
from collections import defaultdict, OrderedDict
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.db.models import F
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
|
|
||||||
from assets.models import Account
|
from assets.models import Account, Type
|
||||||
from assets.serializers import AccountSecretSerializer
|
from assets.serializers import AccountSecretSerializer
|
||||||
from assets.notifications import AccountBackupExecutionTaskMsg
|
from assets.notifications import AccountBackupExecutionTaskMsg
|
||||||
from users.models import User
|
from users.models import User
|
||||||
|
@ -64,34 +64,33 @@ class AssetAccountHandler(BaseAccountHandler):
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_filename(plan_name):
|
def get_filename(plan_name):
|
||||||
filename = os.path.join(
|
filename = os.path.join(
|
||||||
PATH, f'{plan_name}-{_("Asset")}-{local_now_display()}-{time.time()}.xlsx'
|
PATH, f'{plan_name}-{local_now_display()}-{time.time()}.xlsx'
|
||||||
)
|
)
|
||||||
return filename
|
return filename
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def create_data_map(cls):
|
def create_data_map(cls, types: list):
|
||||||
data_map = defaultdict(list)
|
data_map = defaultdict(list)
|
||||||
sheet_name = Account._meta.verbose_name
|
|
||||||
|
|
||||||
accounts = Account.objects.all()
|
# TODO 可以优化一下查询 在账号上做type的缓存 避免数据量大时连表操作
|
||||||
|
accounts = Account.objects.filter(
|
||||||
|
asset__platform__in=types
|
||||||
|
).annotate(type=F('asset__platform__type'))
|
||||||
if not accounts.first():
|
if not accounts.first():
|
||||||
return data_map
|
return data_map
|
||||||
|
|
||||||
|
type_dict = dict(Type.CHOICES)
|
||||||
header_fields = cls.get_header_fields(AccountSecretSerializer(accounts.first()))
|
header_fields = cls.get_header_fields(AccountSecretSerializer(accounts.first()))
|
||||||
for account in accounts:
|
for account in accounts:
|
||||||
account.load_auth()
|
sheet_name = type_dict[account.type]
|
||||||
row = cls.create_row(account, AccountSecretSerializer, header_fields)
|
row = cls.create_row(account, AccountSecretSerializer, header_fields)
|
||||||
if sheet_name not in data_map:
|
if sheet_name not in data_map:
|
||||||
data_map[sheet_name].append(list(row.keys()))
|
data_map[sheet_name].append(list(row.keys()))
|
||||||
data_map[sheet_name].append(list(row.values()))
|
data_map[sheet_name].append(list(row.values()))
|
||||||
|
|
||||||
logger.info('\n\033[33m- 共收集 {} 条资产账号\033[0m'.format(accounts.count()))
|
logger.info('\n\033[33m- 共收集 {} 条账号\033[0m'.format(accounts.count()))
|
||||||
return data_map
|
return data_map
|
||||||
|
|
||||||
handler_map = {
|
|
||||||
'asset': AssetAccountHandler,
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class AccountBackupHandler:
|
class AccountBackupHandler:
|
||||||
def __init__(self, execution):
|
def __init__(self, execution):
|
||||||
|
@ -108,24 +107,21 @@ class AccountBackupHandler:
|
||||||
# Print task start date
|
# Print task start date
|
||||||
time_start = time.time()
|
time_start = time.time()
|
||||||
files = []
|
files = []
|
||||||
for account_type in self.execution.types:
|
types = self.execution.types
|
||||||
handler = handler_map.get(account_type)
|
|
||||||
if not handler:
|
|
||||||
continue
|
|
||||||
|
|
||||||
data_map = handler.create_data_map()
|
data_map = AssetAccountHandler.create_data_map(types)
|
||||||
if not data_map:
|
if not data_map:
|
||||||
continue
|
return files
|
||||||
|
|
||||||
filename = handler.get_filename(self.plan_name)
|
filename = AssetAccountHandler.get_filename(self.plan_name)
|
||||||
|
|
||||||
wb = Workbook(filename)
|
wb = Workbook(filename)
|
||||||
for sheet, data in data_map.items():
|
for sheet, data in data_map.items():
|
||||||
ws = wb.create_sheet(str(sheet))
|
ws = wb.create_sheet(str(sheet))
|
||||||
for row in data:
|
for row in data:
|
||||||
ws.append(row)
|
ws.append(row)
|
||||||
wb.save(filename)
|
wb.save(filename)
|
||||||
files.append(filename)
|
files.append(filename)
|
||||||
timedelta = round((time.time() - time_start), 2)
|
timedelta = round((time.time() - time_start), 2)
|
||||||
logger.info('步骤完成: 用时 {}s'.format(timedelta))
|
logger.info('步骤完成: 用时 {}s'.format(timedelta))
|
||||||
return files
|
return files
|
||||||
|
|
Loading…
Reference in New Issue