perf: 修改 accounts

pull/8931/head
ibuler 2022-09-23 18:59:19 +08:00
parent 286d0e4ac1
commit 234acd6317
16 changed files with 127 additions and 147 deletions

View File

@ -20,6 +20,7 @@ class Protocol(ChoicesMixin, models.TextChoices):
k8s = 'k8s', 'K8S' k8s = 'k8s', 'K8S'
http = 'http', 'HTTP' http = 'http', 'HTTP'
_settings = None
@classmethod @classmethod
def device_protocols(cls): def device_protocols(cls):
@ -106,7 +107,7 @@ class Protocol(ChoicesMixin, models.TextChoices):
@classmethod @classmethod
def settings(cls): def settings(cls):
return { return {
**cls.device_protocols(), **cls.device_protocols(),
**cls.database_protocols(), **cls.database_protocols(),
**cls.cloud_protocols() **cls.cloud_protocols()
} }

View File

@ -34,12 +34,8 @@ class Account(BaseAccount):
] ]
@lazyproperty @lazyproperty
def ip(self): def platform(self):
return self.asset.address return self.asset.platform
@lazyproperty
def asset_name(self):
return self.asset.name
def __str__(self): def __str__(self):
return '{}@{}'.format(self.username, self.asset.name) return '{}@{}'.format(self.username, self.asset.name)

View File

@ -73,11 +73,17 @@ class BaseAccount(OrgModelMixin):
created_by = models.CharField(max_length=128, null=True, verbose_name=_('Created by')) created_by = models.CharField(max_length=128, null=True, verbose_name=_('Created by'))
@property @property
def public_key(self): def has_secret(self):
return '' return bool(self.secret)
@property @property
def private_key(self): def private_key(self):
if self.secret_type == self.SecretType.ssh_key:
return self.secret
return None
@property
def public_key(self):
return '' return ''
@private_key.setter @private_key.setter
@ -94,10 +100,6 @@ class BaseAccount(OrgModelMixin):
self.secret = value self.secret = value
self.secret_type = 'password' self.secret_type = 'password'
def expire_assets_amount(self):
cache_key = self.ASSETS_AMOUNT_CACHE_KEY.format(self.id)
cache.delete(cache_key)
@property @property
def ssh_key_fingerprint(self): def ssh_key_fingerprint(self):
if self.public_key: if self.public_key:
@ -152,53 +154,15 @@ class BaseAccount(OrgModelMixin):
pass pass
return None return None
def set_auth(self, **kwargs):
update_fields = []
for k, v in kwargs.items():
setattr(self, k, v)
update_fields.append(k)
if update_fields:
self.save(update_fields=update_fields)
def _merge_auth(self, other):
if other.password:
self.password = other.password
if other.public_key or other.private_key:
self.private_key = other.private_key
self.public_key = other.public_key
def clear_auth(self):
self.password = ''
self.private_key = ''
self.public_key = ''
self.token = ''
self.save()
@staticmethod @staticmethod
def gen_password(length=36): def gen_password(length=36):
return random_string(length, special_char=True) return random_string(length, special_char=True)
@staticmethod @staticmethod
def gen_key(username): def gen_key(username):
private_key, public_key = ssh_key_gen( private_key, public_key = ssh_key_gen(username=username)
username=username
)
return private_key, public_key return private_key, public_key
def auto_gen_auth(self, password=True, key=True):
_password = None
_private_key = None
_public_key = None
if password:
_password = self.gen_password()
if key:
_private_key, _public_key = self.gen_key(self.username)
self.set_auth(
password=_password, private_key=_private_key,
public_key=_public_key
)
def _to_secret_json(self): def _to_secret_json(self):
"""Push system user use it""" """Push system user use it"""
return { return {

View File

@ -4,6 +4,8 @@ from django.utils.translation import gettext_lazy as _
from assets.const import AllTypes from assets.const import AllTypes
from common.db.fields import JsonDictTextField from common.db.fields import JsonDictTextField
from assets.const import Protocol
__all__ = ['Platform', 'PlatformProtocol', 'PlatformAutomation'] __all__ = ['Platform', 'PlatformProtocol', 'PlatformAutomation']
@ -20,6 +22,13 @@ class PlatformProtocol(models.Model):
setting = models.JSONField(verbose_name=_('Setting'), default=dict) setting = models.JSONField(verbose_name=_('Setting'), default=dict)
platform = models.ForeignKey('Platform', on_delete=models.CASCADE, related_name='protocols') platform = models.ForeignKey('Platform', on_delete=models.CASCADE, related_name='protocols')
def __str__(self):
return '{}/{}'.format(self.name, self.port)
@property
def secret_types(self):
return Protocol.settings().get(self.name, {}).get('secret_types')
class PlatformAutomation(models.Model): class PlatformAutomation(models.Model):
ping_enabled = models.BooleanField(default=False, verbose_name=_("Ping enabled")) ping_enabled = models.BooleanField(default=False, verbose_name=_("Ping enabled"))

View File

@ -1,12 +1,10 @@
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from rest_framework import serializers from rest_framework import serializers
from orgs.mixins.serializers import BulkOrgResourceModelSerializer
from common.drf.serializers import SecretReadableMixin from common.drf.serializers import SecretReadableMixin
from common.drf.fields import ObjectRelatedField from common.drf.fields import ObjectRelatedField
from assets.models import Account, AccountTemplate, Asset from assets.models import Account, AccountTemplate, Asset
from assets.serializers.base import AuthValidateMixin from .base import BaseAccountSerializer
from .common import AccountFieldsSerializerMixin
class AccountSerializerCreateMixin(serializers.ModelSerializer): class AccountSerializerCreateMixin(serializers.ModelSerializer):
@ -28,7 +26,8 @@ class AccountSerializerCreateMixin(serializers.ModelSerializer):
@staticmethod @staticmethod
def replace_attrs(account_template: AccountTemplate, attrs: dict): def replace_attrs(account_template: AccountTemplate, attrs: dict):
exclude_fields = [ exclude_fields = [
'_state', 'org_id', 'id', 'date_created', 'date_updated' '_state', 'org_id', 'id', 'date_created',
'date_updated'
] ]
template_attrs = { template_attrs = {
k: v for k, v in account_template.__dict__.items() k: v for k, v in account_template.__dict__.items()
@ -52,34 +51,36 @@ class AccountSerializerCreateMixin(serializers.ModelSerializer):
return instance return instance
class AccountSerializer( class AccountSerializer(AccountSerializerCreateMixin, BaseAccountSerializer):
AuthValidateMixin, AccountSerializerCreateMixin,
AccountFieldsSerializerMixin, BulkOrgResourceModelSerializer
):
asset = ObjectRelatedField( asset = ObjectRelatedField(
required=False, queryset=Asset.objects, required=False, queryset=Asset.objects,
label=_('Asset'), attrs=('id', 'name', 'address') label=_('Asset'), attrs=('id', 'name', 'address')
) )
platform = serializers.ReadOnlyField(label=_("Platform"))
class Meta(AccountFieldsSerializerMixin.Meta): class Meta(BaseAccountSerializer.Meta):
model = Account model = Account
fields = AccountFieldsSerializerMixin.Meta.fields \ fields = BaseAccountSerializer.Meta.fields \
+ ['su_from', 'version', 'asset'] \
+ ['template', 'push_now'] + ['template', 'push_now']
extra_kwargs = {
**BaseAccountSerializer.Meta.extra_kwargs,
'name': {'required': False, 'allow_null': True},
}
def __init__(self, *args, data=None, **kwargs):
super().__init__(*args, data=data, **kwargs)
if data and 'name' not in data:
data['name'] = data.get('username')
@classmethod @classmethod
def setup_eager_loading(cls, queryset): def setup_eager_loading(cls, queryset):
""" Perform necessary eager loading of data. """ """ Perform necessary eager loading of data. """
queryset = queryset.prefetch_related('asset') queryset = queryset.prefetch_related('asset', 'asset__platform')
return queryset return queryset
class AccountSecretSerializer(SecretReadableMixin, AccountSerializer): class AccountSecretSerializer(SecretReadableMixin, AccountSerializer):
class Meta(AccountSerializer.Meta): class Meta(AccountSerializer.Meta):
fields_backup = [
'name', 'address', 'platform', 'protocols', 'username', 'password',
'private_key', 'public_key', 'date_created', 'date_updated', 'version'
]
extra_kwargs = { extra_kwargs = {
'password': {'write_only': False}, 'password': {'write_only': False},
'private_key': {'write_only': False}, 'private_key': {'write_only': False},

View File

@ -0,0 +1,52 @@
# -*- coding: utf-8 -*-
from io import StringIO
from django.utils.translation import gettext_lazy as _
from rest_framework import serializers
from common.utils import validate_ssh_private_key, ssh_private_key_gen
from common.drf.fields import EncryptedField
from orgs.mixins.serializers import BulkOrgResourceModelSerializer
from assets.models import BaseAccount
__all__ = ['BaseAccountSerializer']
class BaseAccountSerializer(BulkOrgResourceModelSerializer):
secret = EncryptedField(
label=_('Secret'), required=False, allow_blank=True,
allow_null=True, max_length=40960
)
class Meta:
model = BaseAccount
fields_mini = ['id', 'name', 'username']
fields_small = fields_mini + ['privileged', 'secret_type', 'secret', 'has_secret']
fields_other = ['created_by', 'date_created', 'date_updated', 'comment']
fields = fields_small + fields_other
extra_kwargs = {
'secret': {'write_only': True},
'passphrase': {'write_only': True},
}
def validate_private_key(self, private_key):
if not private_key:
return ''
passphrase = self.initial_data.get('passphrase')
passphrase = passphrase if passphrase else None
valid = validate_ssh_private_key(private_key, password=passphrase)
if not valid:
raise serializers.ValidationError(_("private key invalid or passphrase error"))
private_key = ssh_private_key_gen(private_key, password=passphrase)
string_io = StringIO()
private_key.write_private_key(string_io)
private_key = string_io.getvalue()
return private_key
def validate_secret(self, value):
secret_type = self.initial_data.get('secret_type')
if secret_type == 'ssh_key':
value = self.validate_private_key(value)
return value

View File

@ -1,29 +0,0 @@
# -*- coding: utf-8 -*-
#
from rest_framework import serializers
__all__ = ['AccountFieldsSerializerMixin']
class AccountFieldsSerializerMixin(serializers.ModelSerializer):
class Meta:
fields_mini = [
'id', 'name', 'username', 'privileged',
'platform', 'version', 'secret_type',
]
fields_write_only = ['secret', 'passphrase']
fields_other = ['date_created', 'date_updated', 'comment']
fields_small = fields_mini + fields_write_only + fields_other
fields_fk = ['asset']
fields = fields_small + fields_fk
extra_kwargs = {
'secret': {'write_only': True},
'passphrase': {'write_only': True},
'token': {'write_only': True},
'password': {'write_only': True},
}
def validate_name(self, value):
if not value:
return self.initial_data.get('username')
return ''

View File

@ -1,17 +1,14 @@
from assets.models import Account from assets.models import Account
from common.drf.serializers import SecretReadableMixin from common.drf.serializers import SecretReadableMixin
from .common import AccountFieldsSerializerMixin from .base import BaseAccountSerializer
from .account import AccountSerializer, AccountSecretSerializer from .account import AccountSerializer, AccountSecretSerializer
class AccountHistorySerializer(AccountSerializer): class AccountHistorySerializer(AccountSerializer):
class Meta: class Meta:
model = Account.history.model model = Account.history.model
fields = AccountFieldsSerializerMixin.Meta.fields_mini + \ fields = BaseAccountSerializer.Meta.fields_mini
AccountFieldsSerializerMixin.Meta.fields_write_only + \
AccountFieldsSerializerMixin.Meta.fields_fk + \
['history_id', 'date_created', 'date_updated']
read_only_fields = fields read_only_fields = fields
ref_name = 'AccountHistorySerializer' ref_name = 'AccountHistorySerializer'

View File

@ -2,31 +2,16 @@ from django.utils.translation import ugettext_lazy as _
from rest_framework import serializers from rest_framework import serializers
from assets.models import AccountTemplate from assets.models import AccountTemplate
from orgs.mixins.serializers import BulkOrgResourceModelSerializer from .base import BaseAccountSerializer
from assets.serializers.base import AuthValidateMixin
from .common import AccountFieldsSerializerMixin
class AccountTemplateSerializer(AuthValidateMixin, BulkOrgResourceModelSerializer): class AccountTemplateSerializer(BaseAccountSerializer):
class Meta: class Meta(BaseAccountSerializer.Meta):
model = AccountTemplate model = AccountTemplate
fields_mini = ['id', 'name', 'username', 'privileged']
fields_write_only = AccountFieldsSerializerMixin.Meta.fields_write_only
fields_other = AccountFieldsSerializerMixin.Meta.fields_other
fields = fields_mini + fields_write_only + fields_other
extra_kwargs = {
'username': {'required': True},
'name': {'required': True},
'private_key': {'write_only': True},
'public_key': {'write_only': True},
}
def validate(self, attrs):
attrs = self._validate_gen_key(attrs)
return attrs
@classmethod @classmethod
def validate_required(cls, attrs): def validate_required(cls, attrs):
# Todo: why ?
required_field_dict = {} required_field_dict = {}
error = _('This field is required.') error = _('This field is required.')
for k, v in cls().fields.items(): for k, v in cls().fields.items():

View File

@ -1,5 +1,7 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
from io import StringIO
from rest_framework import serializers from rest_framework import serializers
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from django.db.transaction import atomic from django.db.transaction import atomic
@ -7,6 +9,7 @@ from django.db.models import F
from common.drf.serializers import JMSWritableNestedModelSerializer from common.drf.serializers import JMSWritableNestedModelSerializer
from common.drf.fields import LabeledChoiceField, ObjectRelatedField from common.drf.fields import LabeledChoiceField, ObjectRelatedField
from common.utils import validate_ssh_private_key, ssh_private_key_gen
from ..account import AccountSerializer from ..account import AccountSerializer
from ...models import Asset, Node, Platform, Label, Domain, Account, Protocol from ...models import Asset, Node, Platform, Label, Domain, Account, Protocol
from ...const import Category, AllTypes from ...const import Category, AllTypes
@ -47,15 +50,17 @@ class AssetAccountSerializer(AccountSerializer):
class Meta(AccountSerializer.Meta): class Meta(AccountSerializer.Meta):
fields_mini = [ fields_mini = [
'id', 'name', 'username', 'privileged', 'version', 'id', 'name', 'username', 'privileged',
'secret_type', 'version', 'secret_type',
] ]
fields_write_only = [ fields_write_only = [
'secret', 'passphrase', 'push_now' 'secret', 'push_now'
] ]
fields = fields_mini + fields_write_only fields = fields_mini + fields_write_only
class AssetSerializer(JMSWritableNestedModelSerializer): class AssetSerializer(JMSWritableNestedModelSerializer):
category = LabeledChoiceField(choices=Category.choices, read_only=True, label=_('Category')) category = LabeledChoiceField(choices=Category.choices, read_only=True, label=_('Category'))
type = LabeledChoiceField(choices=AllTypes.choices, read_only=True, label=_('Type')) type = LabeledChoiceField(choices=AllTypes.choices, read_only=True, label=_('Type'))

View File

@ -4,7 +4,7 @@ from django.utils.translation import gettext_lazy as _
from common.drf.fields import LabeledChoiceField from common.drf.fields import LabeledChoiceField
from common.drf.serializers import JMSWritableNestedModelSerializer from common.drf.serializers import JMSWritableNestedModelSerializer
from ..models import Platform, PlatformProtocol, PlatformAutomation from ..models import Platform, PlatformProtocol, PlatformAutomation
from ..const import Category, AllTypes from ..const import Category, AllTypes, Protocol
__all__ = ['PlatformSerializer', 'PlatformOpsMethodSerializer'] __all__ = ['PlatformSerializer', 'PlatformOpsMethodSerializer']
@ -64,7 +64,7 @@ class PlatformProtocolsSerializer(serializers.ModelSerializer):
class Meta: class Meta:
model = PlatformProtocol model = PlatformProtocol
fields = ['id', 'name', 'port', 'setting'] fields = ['id', 'name', 'port', 'secret_types', 'setting']
class PlatformSerializer(JMSWritableNestedModelSerializer): class PlatformSerializer(JMSWritableNestedModelSerializer):

View File

@ -23,7 +23,6 @@ __all__ = [
class MethodSerializer(serializers.Serializer): class MethodSerializer(serializers.Serializer):
def __init__(self, method_name=None, **kwargs): def __init__(self, method_name=None, **kwargs):
self.method_name = method_name self.method_name = method_name
super().__init__(**kwargs) super().__init__(**kwargs)

View File

@ -2,13 +2,18 @@
# #
from collections import Iterable from collections import Iterable
from django.db.models import Prefetch, F, NOT_PROVIDED from django.db.models import NOT_PROVIDED
from django.core.exceptions import ObjectDoesNotExist from django.core.exceptions import ObjectDoesNotExist
from rest_framework.utils import html from rest_framework.utils import html
from rest_framework.settings import api_settings from rest_framework.settings import api_settings
from rest_framework.exceptions import ValidationError from rest_framework.exceptions import ValidationError
from rest_framework.fields import SkipField, empty from rest_framework.fields import SkipField, empty
__all__ = ['BulkSerializerMixin', 'BulkListSerializerMixin', 'CommonSerializerMixin', 'CommonBulkSerializerMixin']
__all__ = [
'BulkSerializerMixin', 'BulkListSerializerMixin',
'CommonSerializerMixin', 'CommonBulkSerializerMixin'
]
class BulkSerializerMixin(object): class BulkSerializerMixin(object):
@ -281,20 +286,12 @@ class DynamicFieldsMixin:
self.fields.pop(field, None) self.fields.pop(field, None)
class EagerLoadQuerySetFields:
def setup_eager_loading(self, queryset):
""" Perform necessary eager loading of data. """
queryset = queryset.prefetch_related(
Prefetch('nodes'),
Prefetch('labels'),
).select_related('admin_user', 'domain', 'platform') \
.annotate(platform_base=F('platform__base'))
return queryset
class CommonSerializerMixin(DynamicFieldsMixin, DefaultValueFieldsMixin): class CommonSerializerMixin(DynamicFieldsMixin, DefaultValueFieldsMixin):
instance: None instance: None
initial_data: dict initial_data: dict
common_fields = [
'comment', 'created_by', 'date_created', 'date_updated',
]
def get_initial_value(self, attr, default=None): def get_initial_value(self, attr, default=None):
value = self.initial_data.get(attr) value = self.initial_data.get(attr)
@ -305,6 +302,12 @@ class CommonSerializerMixin(DynamicFieldsMixin, DefaultValueFieldsMixin):
return value return value
return default return default
def get_field_names(self, declared_fields, info):
names = super().get_field_names(declared_fields, info)
common_names = [i for i in self.common_fields if i in names]
primary_names = [i for i in names if i not in self.common_fields]
return primary_names + common_names
class CommonBulkSerializerMixin(BulkSerializerMixin, CommonSerializerMixin): class CommonBulkSerializerMixin(BulkSerializerMixin, CommonSerializerMixin):
pass pass

View File

@ -52,8 +52,6 @@ class OrgResourceStatisticsCache(OrgRelatedCache):
assets_amount = IntegerField() assets_amount = IntegerField()
nodes_amount = IntegerField(queryset=Node.objects) nodes_amount = IntegerField(queryset=Node.objects)
admin_users_amount = IntegerField()
system_users_amount = IntegerField()
domains_amount = IntegerField(queryset=Domain.objects) domains_amount = IntegerField(queryset=Domain.objects)
gateways_amount = IntegerField(queryset=Gateway.objects) gateways_amount = IntegerField(queryset=Gateway.objects)
asset_perms_amount = IntegerField(queryset=AssetPermission.objects) asset_perms_amount = IntegerField(queryset=AssetPermission.objects)

View File

@ -11,8 +11,6 @@ class ResourceStatisticsSerializer(serializers.Serializer):
assets_amount = serializers.IntegerField(required=False) assets_amount = serializers.IntegerField(required=False)
nodes_amount = serializers.IntegerField(required=False) nodes_amount = serializers.IntegerField(required=False)
admin_users_amount = serializers.IntegerField(required=False)
system_users_amount = serializers.IntegerField(required=False)
domains_amount = serializers.IntegerField(required=False) domains_amount = serializers.IntegerField(required=False)
gateways_amount = serializers.IntegerField(required=False) gateways_amount = serializers.IntegerField(required=False)

View File

@ -164,8 +164,9 @@ class BuiltinRole:
@classmethod @classmethod
def sync_to_db(cls, show_msg=False): def sync_to_db(cls, show_msg=False):
roles = cls.get_roles() roles = cls.get_roles()
print("\n Update builtin roles")
for pre_role in roles.values(): for pre_role in roles.values():
role, created = pre_role.update_or_create_role() role, created = pre_role.update_or_create_role()
if show_msg: if show_msg:
print("Update builtin Role: {} - {}".format(role.name, created)) print(" - Update: {} - {}".format(role.name, created))