diff --git a/apps/assets/api/account/account.py b/apps/assets/api/account/account.py index af1575daa..d750fa4a0 100644 --- a/apps/assets/api/account/account.py +++ b/apps/assets/api/account/account.py @@ -34,6 +34,11 @@ class AccountViewSet(OrgBulkModelViewSet): account = super().get_object() task = test_accounts_connectivity_manual.delay([account.id]) return Response(data={'task': task.id}) + # + # @action(methods=['get'], detail=True, url_path='secret') + # def get_secret(self, request, *args, **kwargs): + # account = super().get_object() + # return Response(data={'secret': account.secret}) class AccountSecretsViewSet(RecordViewLogMixin, AccountViewSet): diff --git a/apps/assets/const/base.py b/apps/assets/const/base.py index e39f8c09c..35ad76757 100644 --- a/apps/assets/const/base.py +++ b/apps/assets/const/base.py @@ -35,6 +35,7 @@ class BaseType(TextChoices): if choices == '__self__': choices = [tp] protocols = [{'name': name, **settings.get(name, {})} for name in choices] + protocols[0]['primary'] = True return protocols @classmethod diff --git a/apps/assets/const/types.py b/apps/assets/const/types.py index 0d8b86afa..5cdf5d28e 100644 --- a/apps/assets/const/types.py +++ b/apps/assets/const/types.py @@ -36,6 +36,13 @@ class AllTypes(ChoicesMixin): cls.set_automation_methods(category, tp, constraints) return constraints + @classmethod + def get_primary_protocol_name(cls, category, tp): + constraints = cls.get_constraints(category, tp) + if not constraints: + return None + return constraints.get('protocols')[0]['name'] + @classmethod def set_automation_methods(cls, category, tp, constraints): from assets.automations import filter_platform_methods @@ -189,7 +196,9 @@ class AllTypes(ChoicesMixin): automation.save() platform.protocols.all().delete() - [PlatformProtocol.objects.create(**p, platform=platform) for p in protocols_data] + for p in protocols_data: + p.pop('primary', None) + PlatformProtocol.objects.create(**p, platform=platform) @classmethod def create_or_update_internal_platforms(cls): diff --git a/apps/assets/filters.py b/apps/assets/filters.py index 8e60ac37c..db29f4393 100644 --- a/apps/assets/filters.py +++ b/apps/assets/filters.py @@ -165,6 +165,15 @@ class AccountFilterSet(BaseFilterSet): asset = drf_filters.CharFilter(field_name="asset_id", lookup_expr='exact') assets = drf_filters.CharFilter(field_name='asset_id', lookup_expr='in') nodes = drf_filters.CharFilter(method='filter_nodes') + has_secret = drf_filters.BooleanFilter(method='filter_has_secret') + + @staticmethod + def filter_has_secret(queryset, name, has_secret): + q = Q(secret__isnull=True) | Q(secret='') + if has_secret: + return queryset.exclude(q) + else: + return queryset.filter(q) @staticmethod def filter_nodes(queryset, name, value): diff --git a/apps/assets/migrations/0111_auto_20221017_1441.py b/apps/assets/migrations/0111_auto_20221017_1441.py new file mode 100644 index 000000000..d534cc125 --- /dev/null +++ b/apps/assets/migrations/0111_auto_20221017_1441.py @@ -0,0 +1,23 @@ +# Generated by Django 3.2.14 on 2022-10-17 06:41 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('assets', '0110_gatherfactsautomation'), + ] + + operations = [ + migrations.AddField( + model_name='platformprotocol', + name='default', + field=models.BooleanField(default=True, verbose_name='Default'), + ), + migrations.AddField( + model_name='platformprotocol', + name='required', + field=models.BooleanField(default=False, verbose_name='Required'), + ), + ] diff --git a/apps/assets/models/asset/common.py b/apps/assets/models/asset/common.py index 5703c82dc..573091959 100644 --- a/apps/assets/models/asset/common.py +++ b/apps/assets/models/asset/common.py @@ -135,7 +135,10 @@ class Asset(AbsConnectivity, NodesRelationMixin, JMSOrgBaseModel): @lazyproperty def primary_protocol(self): - return self.protocols.first() + from assets.const.types import AllTypes + primary_protocol_name = AllTypes.get_primary_protocol_name(self.category, self.type) + protocol = self.protocols.get(name=primary_protocol_name) + return protocol @lazyproperty def protocol(self): diff --git a/apps/assets/models/base.py b/apps/assets/models/base.py index 7b5b1d6f8..56de5fbac 100644 --- a/apps/assets/models/base.py +++ b/apps/assets/models/base.py @@ -72,18 +72,13 @@ class BaseAccount(OrgModelMixin): date_updated = models.DateTimeField(auto_now=True, verbose_name=_("Date updated")) created_by = models.CharField(max_length=128, null=True, verbose_name=_('Created by')) - @property - def has_secret(self): - return bool(self.secret) - @property def password(self): return self.secret - @password.setter - def password(self, value): - self.secret = value - self.secret_type = 'password' + @property + def has_secret(self): + return bool(self.secret) @property def private_key(self): diff --git a/apps/assets/models/platform.py b/apps/assets/models/platform.py index 3c32fbcb1..f89221448 100644 --- a/apps/assets/models/platform.py +++ b/apps/assets/models/platform.py @@ -17,6 +17,8 @@ class PlatformProtocol(models.Model): 'sftp_enabled': True, 'sftp_home': '/tmp' } + default = models.BooleanField(default=False, verbose_name=_('Default')) + required = models.BooleanField(default=False, verbose_name=_('Required')) name = models.CharField(max_length=32, verbose_name=_('Name')) port = models.IntegerField(verbose_name=_('Port')) setting = models.JSONField(verbose_name=_('Setting'), default=dict) @@ -25,6 +27,13 @@ class PlatformProtocol(models.Model): def __str__(self): return '{}/{}'.format(self.name, self.port) + @property + def primary(self): + primary_protocol_name = AllTypes.get_primary_protocol_name( + self.platform.category, self.platform.type + ) + return self.name == primary_protocol_name + @property def secret_types(self): return Protocol.settings().get(self.name, {}).get('secret_types') @@ -83,9 +92,10 @@ class Platform(models.Model): ) return linux.id - @staticmethod - def set_default_platforms_ops(platform_model): - pass + @property + def primary_protocol(self): + primary_protocol_name = AllTypes.get_primary_protocol_name(self.category, self.type) + return self.protocols.filter(name=primary_protocol_name).first() def __str__(self): return self.name diff --git a/apps/assets/serializers/account/account.py b/apps/assets/serializers/account/account.py index ecf8e8490..77658c0fc 100644 --- a/apps/assets/serializers/account/account.py +++ b/apps/assets/serializers/account/account.py @@ -82,9 +82,7 @@ class AccountSerializer(AccountSerializerCreateMixin, BaseAccountSerializer): class AccountSecretSerializer(SecretReadableMixin, AccountSerializer): class Meta(AccountSerializer.Meta): extra_kwargs = { - 'password': {'write_only': False}, - 'private_key': {'write_only': False}, - 'public_key': {'write_only': False}, + 'secret': {'write_only': False}, } diff --git a/apps/assets/serializers/asset/common.py b/apps/assets/serializers/asset/common.py index 4aef50f58..45b02f8ac 100644 --- a/apps/assets/serializers/asset/common.py +++ b/apps/assets/serializers/asset/common.py @@ -117,6 +117,34 @@ class AssetSerializer(JMSWritableNestedModelSerializer): if not node_id: return [] + def validate_protocols(self, protocols_data): + if not protocols_data: + protocols_data = [] + platform_id = self.initial_data.get('platform') + if isinstance(platform_id, dict): + platform_id = platform_id.get('id') or platform_id.get('pk') + platform = Platform.objects.filter(id=platform_id).first() + if not platform: + raise serializers.ValidationError({'platform': _("Platform not exist")}) + + protocols_data_map = {p['name']: p for p in protocols_data} + platform_protocols = platform.protocols.all() + protocols_default = [p for p in platform_protocols if p.default] + protocols_required = [p for p in platform_protocols if p.required or p.primary] + + if not protocols_data_map: + protocols_data_map = { + p.name: {'name': p.name, 'port': p.port} + for p in protocols_required + protocols_default + } + + protocols_not_found = [p.name for p in protocols_required if p.name not in protocols_data_map] + if protocols_not_found: + raise serializers.ValidationError({ + 'protocols': _("Protocol is required: {}").format(', '.join(protocols_not_found)) + }) + return protocols_data_map.values() + @atomic def create(self, validated_data): nodes_display = validated_data.pop('nodes_display', '') diff --git a/apps/assets/serializers/platform.py b/apps/assets/serializers/platform.py index 0baeceb8d..06b70be05 100644 --- a/apps/assets/serializers/platform.py +++ b/apps/assets/serializers/platform.py @@ -50,9 +50,9 @@ class PlatformAutomationSerializer(serializers.ModelSerializer): 'gather_facts_method': {'label': '收集信息方式'}, 'verify_account_enabled': {'label': '启用校验账号'}, 'verify_account_method': {'label': '校验账号方式'}, - 'create_account_enabled': {'label': '启用创建账号'}, - 'create_account_method': {'label': '创建账号方式'}, - 'change_secret_enabled': {'label': '启用账号创建改密'}, + 'create_account_enabled': {'label': '启用推送账号'}, + 'create_account_method': {'label': '推送账号方式'}, + 'change_secret_enabled': {'label': '启用账号改密'}, 'change_secret_method': {'label': '账号创建改密方式'}, 'gather_accounts_enabled': {'label': '启用账号收集'}, 'gather_accounts_method': {'label': '收集账号方式'}, @@ -61,10 +61,14 @@ class PlatformAutomationSerializer(serializers.ModelSerializer): class PlatformProtocolsSerializer(serializers.ModelSerializer): setting = ProtocolSettingSerializer(required=False, allow_null=True) + primary = serializers.BooleanField(read_only=True, label=_("Primary")) class Meta: model = PlatformProtocol - fields = ['id', 'name', 'port', 'secret_types', 'setting'] + fields = [ + 'id', 'name', 'port', 'primary', 'default', + 'required', 'secret_types', 'setting', + ] class PlatformSerializer(JMSWritableNestedModelSerializer): diff --git a/apps/common/db/fields.py b/apps/common/db/fields.py index 0757bc068..72c4df898 100644 --- a/apps/common/db/fields.py +++ b/apps/common/db/fields.py @@ -117,10 +117,10 @@ class EncryptMixin: return signer.unsign(value) or '' def from_db_value(self, value, expression, connection, context=None): - if value is None: + if not value: return value - value = force_text(value) + value = force_text(value) plain_value = crypto.decrypt(value) # 如果没有解开,使用原来的signer解密 @@ -134,7 +134,7 @@ class EncryptMixin: return plain_value def get_prep_value(self, value): - if value is None: + if not value: return value # 先 json 再解密 diff --git a/apps/common/drf/serializers/common.py b/apps/common/drf/serializers/common.py index 836914a60..cfd81d476 100644 --- a/apps/common/drf/serializers/common.py +++ b/apps/common/drf/serializers/common.py @@ -86,5 +86,5 @@ class GroupedChoiceSerializer(ChoiceSerializer): children = ChoiceSerializer(many=True, label=_("Children")) -class JMSWritableNestedModelSerializer(WritableNestedModelSerializer): +class JMSWritableNestedModelSerializer(ModelSerializer): pass