From 7842e3e5abcb45cacdf793e584ccb49b110391cc Mon Sep 17 00:00:00 2001 From: "Jiangjie.Bai" Date: Tue, 6 Dec 2022 11:03:14 +0800 Subject: [PATCH] Merge: v3 to dev (#9160) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: 修改 ConnectionTokenSecretSerializer * perf: connect token secret (#9155) Co-authored-by: feng <1304903146@qq.com> Co-authored-by: Jiangjie.Bai * feat: 作业迁移至个人级别 * perf: asset enabled (#9157) Co-authored-by: feng <1304903146@qq.com> * perf: 修改ConnectionTokenSecret Gateway数据结构; 修改Domain Gateway Model方法 * perf: ConnectionTokenSecret 返回 domain 信息 * refactor: 移动 Gateway Model 到 asset 目录下 * refactor: 移动 Gateway Model 单独到 gateway 文件中 * perf: 修改 GatewaySerializer 目录 * perf: 修改 GatewaySerializer 目录 Co-authored-by: fit2bot <68588906+fit2bot@users.noreply.github.com> Co-authored-by: feng <1304903146@qq.com> Co-authored-by: Aaron3S --- apps/assets/models/__init__.py | 1 + apps/assets/models/asset/host.py | 3 +- apps/assets/models/base.py | 12 ++ apps/assets/models/domain.py | 166 ++---------------- apps/assets/models/gateway.py | 121 +++++++++++++ apps/assets/serializers/__init__.py | 2 +- apps/assets/serializers/account/__init__.py | 1 + apps/assets/serializers/asset/common.py | 12 +- apps/assets/serializers/domain.py | 50 ++---- apps/assets/serializers/gateway.py | 11 ++ .../authentication/models/connection_token.py | 4 +- .../serializers/connect_token_secret.py | 46 +++-- .../serializers/connection_token.py | 3 +- apps/ops/api/adhoc.py | 10 +- apps/ops/api/job.py | 14 +- apps/ops/api/playbook.py | 4 +- .../ops/migrations/0028_auto_20221205_1627.py | 34 ++++ apps/ops/models/adhoc.py | 5 +- apps/ops/models/job.py | 8 +- apps/ops/models/playbook.py | 4 +- apps/ops/serializers/adhoc.py | 3 +- apps/ops/serializers/job.py | 11 +- apps/ops/serializers/playbook.py | 2 +- 23 files changed, 284 insertions(+), 243 deletions(-) create mode 100644 apps/assets/models/gateway.py create mode 100644 apps/assets/serializers/gateway.py create mode 100644 apps/ops/migrations/0028_auto_20221205_1627.py diff --git a/apps/assets/models/__init__.py b/apps/assets/models/__init__.py index 376355657..6d9716c39 100644 --- a/apps/assets/models/__init__.py +++ b/apps/assets/models/__init__.py @@ -3,6 +3,7 @@ from .platform import * from .asset import * from .label import Label from .group import * +from .gateway import * from .domain import * from .node import * from .utils import * diff --git a/apps/assets/models/asset/host.py b/apps/assets/models/asset/host.py index 6ca93b89f..7594fe25a 100644 --- a/apps/assets/models/asset/host.py +++ b/apps/assets/models/asset/host.py @@ -1,6 +1,7 @@ -from assets.const import GATEWAY_NAME from .common import Asset +__all__ = ['Host'] + class Host(Asset): pass diff --git a/apps/assets/models/base.py b/apps/assets/models/base.py index 0885f5b88..03697d427 100644 --- a/apps/assets/models/base.py +++ b/apps/assets/models/base.py @@ -50,6 +50,16 @@ class AbsConnectivity(models.Model): abstract = True +class BaseAccountQuerySet(models.QuerySet): + def active(self): + return self.filter(is_active=True) + + +class BaseAccountManager(models.Manager): + def active(self): + return self.get_queryset().active() + + class BaseAccount(JMSOrgBaseModel): name = models.CharField(max_length=128, verbose_name=_("Name")) username = models.CharField(max_length=128, blank=True, verbose_name=_('Username'), db_index=True) @@ -62,6 +72,8 @@ class BaseAccount(JMSOrgBaseModel): comment = models.TextField(blank=True, verbose_name=_('Comment')) created_by = models.CharField(max_length=128, null=True, verbose_name=_('Created by')) + objects = BaseAccountManager.from_queryset(BaseAccountQuerySet)() + @property def has_secret(self): return bool(self.secret) diff --git a/apps/assets/models/domain.py b/apps/assets/models/domain.py index e9894f72c..77758283d 100644 --- a/apps/assets/models/domain.py +++ b/apps/assets/models/domain.py @@ -2,21 +2,18 @@ # import uuid import random -import socket -import paramiko from django.db import models from django.utils.translation import ugettext_lazy as _ from common.utils import get_logger, lazyproperty from orgs.mixins.models import OrgModelMixin -from assets.models import Host, Platform -from assets.const import GATEWAY_NAME, SecretType, Connectivity -from orgs.mixins.models import OrgManager + +from .gateway import Gateway logger = get_logger(__file__) -__all__ = ['Domain', 'Gateway'] +__all__ = ['Domain'] class Domain(OrgModelMixin): @@ -33,159 +30,26 @@ class Domain(OrgModelMixin): def __str__(self): return self.name - @classmethod - def get_gateway_queryset(cls): - return Gateway.objects.all() - - @lazyproperty - def gateways(self): - return self.get_gateway_queryset().filter(domain=self, is_active=True) - def select_gateway(self): return self.random_gateway() def random_gateway(self): - gateways = [gw for gw in self.gateways if gw.is_connective] - if gateways: - return random.choice(gateways) - - logger.warn(f'Gateway all bad. domain={self}, gateway_num={len(self.gateways)}.') - if self.gateways: - return random.choice(self.gateways) - - -class GatewayManager(OrgManager): - def get_queryset(self): - queryset = super().get_queryset() - queryset = queryset.filter(platform__name=GATEWAY_NAME) - return queryset - - def bulk_create(self, objs, batch_size=None, ignore_conflicts=False): - platform = Gateway().default_platform - for obj in objs: - obj.platform_id = platform.id - return super().bulk_create(objs, batch_size, ignore_conflicts) - - -class Gateway(Host): - objects = GatewayManager() - - class Meta: - proxy = True + gateways = [gw for gw in self.active_gateways if gw.is_connective] + if not gateways: + gateways = self.active_gateways + logger.warn(f'Gateway all bad. domain={self}, gateway_num={len(gateways)}.') + return random.choice(gateways) @lazyproperty - def default_platform(self): - return Platform.objects.get(name=GATEWAY_NAME, internal=True) - - def save(self, *args, **kwargs): - platform = self.default_platform - self.platform_id = platform.id - return super().save(*args, **kwargs) + def active_gateways(self): + return self.gateways.filter(is_active=True) @lazyproperty - def select_accounts(self) -> dict: - account_dict = {} - accounts = self.accounts.filter(is_active=True).order_by('-privileged', '-date_updated') - password_account = accounts.filter(secret_type=SecretType.PASSWORD).first() - if password_account: - account_dict[SecretType.PASSWORD] = password_account + def gateways(self): + return self.get_gateway_queryset().filter(domain=self) - ssh_key_account = accounts.filter(secret_type=SecretType.SSH_KEY).first() - if ssh_key_account: - account_dict[SecretType.SSH_KEY] = ssh_key_account - return account_dict + @classmethod + def get_gateway_queryset(cls): + return Gateway.objects.all() - @property - def password(self): - account = self.select_accounts.get(SecretType.PASSWORD) - return account.secret if account else None - @property - def private_key(self): - account = self.select_accounts.get(SecretType.SSH_KEY) - return account.private_key if account else None - - @property - def private_key_obj(self): - account = self.select_accounts.get(SecretType.SSH_KEY) - return account.private_key_obj if account else None - - @property - def private_key_path(self): - account = self.select_accounts.get(SecretType.SSH_KEY) - return account.private_key_path if account else None - - @lazyproperty - def username(self): - accounts = self.select_accounts.values() - if len(accounts) == 0: - return None - accounts = sorted( - accounts, key=lambda x: x['privileged'], reverse=True - ) - return accounts[0].username - - def test_connective(self, local_port=None): - local_port = self.port if local_port is None else local_port - client = paramiko.SSHClient() - client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) - proxy = paramiko.SSHClient() - proxy.set_missing_host_key_policy(paramiko.AutoAddPolicy()) - - try: - proxy.connect( - self.address, - port=self.port, - username=self.username, - password=self.password, - pkey=self.private_key_obj - ) - except( - paramiko.AuthenticationException, - paramiko.BadAuthenticationType, - paramiko.SSHException, - paramiko.ChannelException, - paramiko.ssh_exception.NoValidConnectionsError, - socket.gaierror - ) as e: - err = str(e) - if err.startswith('[Errno None] Unable to connect to port'): - err = _('Unable to connect to port {port} on {address}') - err = err.format(port=self.port, address=self.address) - elif err == 'Authentication failed.': - err = _('Authentication failed') - elif err == 'Connect failed': - err = _('Connect failed') - self.set_connectivity(Connectivity.FAILED) - return False, err - - try: - sock = proxy.get_transport().open_channel( - 'direct-tcpip', ('127.0.0.1', local_port), ('127.0.0.1', 0) - ) - client.connect( - '127.0.0.1', - sock=sock, - timeout=5, - port=local_port, - username=self.username, - password=self.password, - key_filename=self.private_key_path, - ) - except ( - paramiko.SSHException, - paramiko.ssh_exception.SSHException, - paramiko.ChannelException, - paramiko.AuthenticationException, - TimeoutError - ) as e: - - err = getattr(e, 'text', str(e)) - if err == 'Connect failed': - err = _('Connect failed') - self.set_connectivity(Connectivity.FAILED) - return False, err - finally: - client.close() - self.set_connectivity(Connectivity.OK) - return True, None diff --git a/apps/assets/models/gateway.py b/apps/assets/models/gateway.py new file mode 100644 index 000000000..4664e11a8 --- /dev/null +++ b/apps/assets/models/gateway.py @@ -0,0 +1,121 @@ +# -*- coding: utf-8 -*- +# +import socket +import paramiko + +from django.utils.translation import ugettext_lazy as _ + +from common.utils import get_logger, lazyproperty +from orgs.mixins.models import OrgManager + +from assets.const import GATEWAY_NAME, Connectivity +from assets.models.platform import Platform +from assets.models.account import Account + +from .asset.host import Host + +logger = get_logger(__file__) + +__all__ = ['Gateway'] + + +class GatewayManager(OrgManager): + def get_queryset(self): + queryset = super().get_queryset() + queryset = queryset.filter(platform__name=GATEWAY_NAME) + return queryset + + def bulk_create(self, objs, batch_size=None, ignore_conflicts=False): + default_platform = Gateway.default_platform() + for obj in objs: + obj.platform = default_platform + return super().bulk_create(objs, batch_size, ignore_conflicts) + + +class Gateway(Host): + objects = GatewayManager() + + class Meta: + proxy = True + + def save(self, *args, **kwargs): + self.platform = self.default_platform() + return super().save(*args, **kwargs) + + @classmethod + def default_platform(cls): + return Platform.objects.get(name=GATEWAY_NAME, internal=True) + + @lazyproperty + def select_account(self): + account = self.accounts.active().order_by('-privileged', '-date_updated').first() + return account + + def test_connective(self, local_port=None): + local_port = self.port if local_port is None else local_port + client = paramiko.SSHClient() + client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) + proxy = paramiko.SSHClient() + proxy.set_missing_host_key_policy(paramiko.AutoAddPolicy()) + if not isinstance(self.select_account, Account): + err = _('No account') + return False, err + + logger.debug('Test account: {}'.format(self.select_account)) + try: + proxy.connect( + self.address, + port=self.port, + username=self.select_account.username, + password=self.select_account.secret, + pkey=self.select_account.private_key_obj + ) + except( + paramiko.AuthenticationException, + paramiko.BadAuthenticationType, + paramiko.SSHException, + paramiko.ChannelException, + paramiko.ssh_exception.NoValidConnectionsError, + socket.gaierror + ) as e: + err = str(e) + if err.startswith('[Errno None] Unable to connect to port'): + err = _('Unable to connect to port {port} on {address}') + err = err.format(port=self.port, address=self.address) + elif err == 'Authentication failed.': + err = _('Authentication failed') + elif err == 'Connect failed': + err = _('Connect failed') + self.set_connectivity(Connectivity.FAILED) + return False, err + + try: + sock = proxy.get_transport().open_channel( + 'direct-tcpip', ('127.0.0.1', local_port), ('127.0.0.1', 0) + ) + client.connect( + '127.0.0.1', + sock=sock, + timeout=5, + port=local_port, + username=self.select_account.username, + password=self.select_account.secret, + key_filename=self.select_account.private_key_path, + ) + except ( + paramiko.SSHException, + paramiko.ssh_exception.SSHException, + paramiko.ChannelException, + paramiko.AuthenticationException, + TimeoutError + ) as e: + + err = getattr(e, 'text', str(e)) + if err == 'Connect failed': + err = _('Connect failed') + self.set_connectivity(Connectivity.FAILED) + return False, err + finally: + client.close() + self.set_connectivity(Connectivity.OK) + return True, None diff --git a/apps/assets/serializers/__init__.py b/apps/assets/serializers/__init__.py index 9876e3aa6..7d82131a7 100644 --- a/apps/assets/serializers/__init__.py +++ b/apps/assets/serializers/__init__.py @@ -4,11 +4,11 @@ from .asset import * from .label import * from .node import * +from .gateway import * from .domain import * from .gathered_user import * from .favorite_asset import * from .account import * -from assets.serializers.account.backup import * from .platform import * from .cagegory import * from .automations import * diff --git a/apps/assets/serializers/account/__init__.py b/apps/assets/serializers/account/__init__.py index 062b7064c..1e5dde298 100644 --- a/apps/assets/serializers/account/__init__.py +++ b/apps/assets/serializers/account/__init__.py @@ -1,2 +1,3 @@ from .account import * from .template import * +from .backup import * diff --git a/apps/assets/serializers/asset/common.py b/apps/assets/serializers/asset/common.py index 0fd9e9e8a..188539981 100644 --- a/apps/assets/serializers/asset/common.py +++ b/apps/assets/serializers/asset/common.py @@ -67,11 +67,11 @@ class AssetSerializer(OrgResourceSerializerMixin, WritableNestedModelSerializer) labels = AssetLabelSerializer(many=True, required=False, label=_('Labels')) protocols = AssetProtocolsSerializer(many=True, required=False, label=_('Protocols')) accounts = AssetAccountSerializer(many=True, required=False, label=_('Accounts')) - automation_enabled_info = serializers.SerializerMethodField() + enabled_info = serializers.SerializerMethodField() class Meta: model = Asset - fields_mini = ['id', 'name', 'address', 'automation_enabled_info'] + fields_mini = ['id', 'name', 'address', 'enabled_info'] fields_small = fields_mini + ['is_active', 'comment'] fields_fk = ['domain', 'platform', 'platform'] fields_m2m = [ @@ -95,11 +95,15 @@ class AssetSerializer(OrgResourceSerializerMixin, WritableNestedModelSerializer) return names @staticmethod - def get_automation_enabled_info(obj): - automation = obj.platform.automation + def get_enabled_info(obj): + platform = obj.platform + automation = platform.automation return { + 'su_enabled': platform.su_enabled, 'ping_enabled': automation.ping_enabled, + 'domain_enabled': platform.domain_enabled, 'ansible_enabled': automation.ansible_enabled, + 'protocols_enabled': platform.protocols_enabled, 'gather_facts_enabled': automation.gather_facts_enabled, 'push_account_enabled': automation.push_account_enabled, 'change_secret_enabled': automation.change_secret_enabled, diff --git a/apps/assets/serializers/domain.py b/apps/assets/serializers/domain.py index b06a1afc8..8fc711146 100644 --- a/apps/assets/serializers/domain.py +++ b/apps/assets/serializers/domain.py @@ -1,18 +1,20 @@ # -*- coding: utf-8 -*- # -from rest_framework import serializers from django.utils.translation import ugettext_lazy as _ from orgs.mixins.serializers import BulkOrgResourceModelSerializer -from common.drf.serializers import SecretReadableMixin from common.drf.fields import ObjectRelatedField -from ..serializers import HostSerializer -from ..models import Domain, Gateway, Asset +from ..serializers import GatewaySerializer +from ..models import Domain, Asset + + +__all__ = ['DomainSerializer', 'DomainWithGatewaySerializer'] class DomainSerializer(BulkOrgResourceModelSerializer): - asset_count = serializers.SerializerMethodField(label=_('Assets amount')) - gateway_count = serializers.SerializerMethodField(label=_('Gateways count')) + gateways = ObjectRelatedField( + many=True, required=False, queryset=Asset.objects, label=_('Gateway') + ) assets = ObjectRelatedField( many=True, required=False, queryset=Asset.objects, label=_('Asset') ) @@ -21,40 +23,10 @@ class DomainSerializer(BulkOrgResourceModelSerializer): model = Domain fields_mini = ['id', 'name'] fields_small = fields_mini + ['comment'] - fields_m2m = ['assets'] - read_only_fields = ['asset_count', 'gateway_count', 'date_created'] + fields_m2m = ['assets', 'gateways'] + read_only_fields = ['date_created'] fields = fields_small + fields_m2m + read_only_fields - - extra_kwargs = { - 'assets': {'required': False, 'label': _('Assets')}, - } - - @staticmethod - def get_asset_count(obj): - return obj.assets.count() - - @staticmethod - def get_gateway_count(obj): - return obj.gateways.count() - - -class GatewaySerializer(HostSerializer): - effective_accounts = serializers.SerializerMethodField() - - class Meta(HostSerializer.Meta): - model = Gateway - fields = HostSerializer.Meta.fields + ['effective_accounts'] - - @staticmethod - def get_effective_accounts(obj): - accounts = obj.select_accounts.values() - return [ - { - 'id': account.id, - 'username': account.username, - 'secret_type': account.secret_type, - } for account in accounts - ] + extra_kwargs = {} class DomainWithGatewaySerializer(BulkOrgResourceModelSerializer): diff --git a/apps/assets/serializers/gateway.py b/apps/assets/serializers/gateway.py new file mode 100644 index 000000000..8dab64957 --- /dev/null +++ b/apps/assets/serializers/gateway.py @@ -0,0 +1,11 @@ +# -*- coding: utf-8 -*- +# +from ..serializers import HostSerializer +from ..models import Gateway + +__all__ = ['GatewaySerializer'] + + +class GatewaySerializer(HostSerializer): + class Meta(HostSerializer.Meta): + model = Gateway diff --git a/apps/authentication/models/connection_token.py b/apps/authentication/models/connection_token.py index b30ccb376..4a23d18b8 100644 --- a/apps/authentication/models/connection_token.py +++ b/apps/authentication/models/connection_token.py @@ -153,7 +153,7 @@ class ConnectionToken(OrgModelMixin, JMSBaseModel): if not self.domain: return self.domain: Domain - return self.domain.random_gateway() + return self.domain.select_gateway() @lazyproperty def command_filter_acls(self): @@ -161,7 +161,7 @@ class ConnectionToken(OrgModelMixin, JMSBaseModel): kwargs = { 'user': self.user, 'asset': self.asset, - 'account': self.account, + 'account': self.account_object, } acls = CommandFilterACL.filter_queryset(**kwargs).valid() return acls diff --git a/apps/authentication/serializers/connect_token_secret.py b/apps/authentication/serializers/connect_token_secret.py index 48f86ba50..f24f3e9c6 100644 --- a/apps/authentication/serializers/connect_token_secret.py +++ b/apps/authentication/serializers/connect_token_secret.py @@ -1,13 +1,16 @@ from django.utils.translation import ugettext_lazy as _ from rest_framework import serializers -from acls.models import CommandGroup -from assets.models import Asset, Account, Platform +from common.drf.fields import ObjectRelatedField +from acls.models import CommandGroup, CommandFilterACL +from assets.models import Asset, Account, Platform, Gateway, Domain from assets.serializers import PlatformSerializer, AssetProtocolsSerializer -from authentication.models import ConnectionToken -from orgs.mixins.serializers import OrgResourceModelSerializerMixin -from perms.serializers.permission import ActionChoicesField from users.models import User +from perms.serializers.permission import ActionChoicesField +from orgs.mixins.serializers import OrgResourceModelSerializerMixin + +from ..models import ConnectionToken + __all__ = [ 'ConnectionTokenSecretSerializer', @@ -53,21 +56,33 @@ class _ConnectionTokenAccountSerializer(serializers.ModelSerializer): class _ConnectionTokenGatewaySerializer(serializers.ModelSerializer): """ Gateway """ + account = ObjectRelatedField( + required=False, source='select_account', queryset=Account.objects, + attrs=('id', 'name', 'username', 'secret', 'secret_type') + ) + protocols = AssetProtocolsSerializer(many=True, required=False, label=_('Protocols')) + class Meta: - model = Asset + model = Gateway fields = [ - 'id', 'address', 'port', - # 'username', 'password', 'private_key' + 'id', 'name', 'address', 'protocols', 'account' ] -class _ConnectionTokenACLCmdGroupSerializer(serializers.ModelSerializer): - """ ACL command group""" +class _ConnectionTokenCommandFilterACLSerializer(serializers.ModelSerializer): + command_groups = ObjectRelatedField( + many=True, required=False, queryset=CommandGroup.objects, + attrs=('id', 'name', 'type', 'content', 'ignore_case', 'pattern'), + label=_('Command group') + ) + reviewers = ObjectRelatedField( + many=True, queryset=User.objects, label=_("Reviewers"), required=False + ) class Meta: - model = CommandGroup + model = CommandFilterACL fields = [ - 'id', 'type', 'content', 'ignore_case', 'pattern' + 'id', 'name', 'command_groups', 'action', 'reviewers', 'priority', 'is_active' ] @@ -87,7 +102,8 @@ class ConnectionTokenSecretSerializer(OrgResourceModelSerializerMixin): account = _ConnectionTokenAccountSerializer(read_only=True, source='account_object') gateway = _ConnectionTokenGatewaySerializer(read_only=True) platform = _ConnectionTokenPlatformSerializer(read_only=True) - acl_command_groups = _ConnectionTokenACLCmdGroupSerializer(read_only=True, many=True) + domain = ObjectRelatedField(queryset=Domain.objects, required=False, label=_('Domain')) + command_filter_acls = _ConnectionTokenCommandFilterACLSerializer(read_only=True, many=True) actions = ActionChoicesField() expire_at = serializers.IntegerField() expire_now = serializers.BooleanField(label=_('Expired now'), write_only=True, default=True) @@ -97,8 +113,8 @@ class ConnectionTokenSecretSerializer(OrgResourceModelSerializerMixin): model = ConnectionToken fields = [ 'id', 'value', 'user', 'asset', 'account', - 'platform', 'acl_command_groups', 'protocol', - 'gateway', 'actions', 'expire_at', 'expire_now', + 'platform', 'command_filter_acls', 'protocol', + 'domain', 'gateway', 'actions', 'expire_at', 'expire_now', 'connect_method' ] extra_kwargs = { diff --git a/apps/authentication/serializers/connection_token.py b/apps/authentication/serializers/connection_token.py index 46d3f453a..2b5b156e8 100644 --- a/apps/authentication/serializers/connection_token.py +++ b/apps/authentication/serializers/connection_token.py @@ -1,9 +1,10 @@ from django.utils.translation import ugettext_lazy as _ from rest_framework import serializers -from authentication.models import ConnectionToken from orgs.mixins.serializers import OrgResourceModelSerializerMixin +from ..models import ConnectionToken + __all__ = [ 'ConnectionTokenSerializer', 'SuperConnectionTokenSerializer', ] diff --git a/apps/ops/api/adhoc.py b/apps/ops/api/adhoc.py index aca7047f1..2713d448f 100644 --- a/apps/ops/api/adhoc.py +++ b/apps/ops/api/adhoc.py @@ -1,9 +1,9 @@ # -*- coding: utf-8 -*- # -from rest_framework import viewsets +from rest_framework_bulk import BulkModelViewSet -from orgs.mixins.api import OrgBulkModelViewSet +from common.mixins import CommonApiMixin from ..models import AdHoc from ..serializers import ( AdHocSerializer @@ -14,7 +14,9 @@ __all__ = [ ] -class AdHocViewSet(OrgBulkModelViewSet): +class AdHocViewSet(CommonApiMixin, BulkModelViewSet): serializer_class = AdHocSerializer permission_classes = () - model = AdHoc + + def get_queryset(self): + return AdHoc.objects.filter(creator=self.request.user) diff --git a/apps/ops/api/job.py b/apps/ops/api/job.py index c5dfbc1e1..a7ba44f22 100644 --- a/apps/ops/api/job.py +++ b/apps/ops/api/job.py @@ -1,12 +1,13 @@ from rest_framework import viewsets +from rest_framework_bulk import BulkModelViewSet +from common.mixins import CommonApiMixin from ops.models import Job, JobExecution from ops.serializers.job import JobSerializer, JobExecutionSerializer __all__ = ['JobViewSet', 'JobExecutionViewSet'] from ops.tasks import run_ops_job_execution -from orgs.mixins.api import OrgBulkModelViewSet def set_task_to_serializer_data(serializer, task): @@ -15,13 +16,12 @@ def set_task_to_serializer_data(serializer, task): setattr(serializer, "_data", data) -class JobViewSet(OrgBulkModelViewSet): +class JobViewSet(CommonApiMixin, BulkModelViewSet): serializer_class = JobSerializer - model = Job permission_classes = () def get_queryset(self): - query_set = super().get_queryset() + query_set = Job.objects.filter(creator=self.request.user) if self.action != 'retrieve': return query_set.filter(instant=False) return query_set @@ -45,11 +45,10 @@ class JobViewSet(OrgBulkModelViewSet): set_task_to_serializer_data(serializer, task) -class JobExecutionViewSet(OrgBulkModelViewSet): +class JobExecutionViewSet(CommonApiMixin, BulkModelViewSet): serializer_class = JobExecutionSerializer http_method_names = ('get', 'post', 'head', 'options',) permission_classes = () - model = JobExecution def perform_create(self, serializer): instance = serializer.save() @@ -57,7 +56,8 @@ class JobExecutionViewSet(OrgBulkModelViewSet): set_task_to_serializer_data(serializer, task) def get_queryset(self): - query_set = super().get_queryset() + query_set = JobExecution.objects.filter(creator=self.request.user) + query_set = query_set.filter(creator=self.request.user) job_id = self.request.query_params.get('job_id') if job_id: query_set = query_set.filter(job_id=job_id) diff --git a/apps/ops/api/playbook.py b/apps/ops/api/playbook.py index e8cf1ff16..9ce525c28 100644 --- a/apps/ops/api/playbook.py +++ b/apps/ops/api/playbook.py @@ -2,7 +2,9 @@ import os import zipfile from django.conf import settings +from rest_framework_bulk import BulkModelViewSet +from common.mixins import CommonApiMixin from orgs.mixins.api import OrgBulkModelViewSet from ..exception import PlaybookNoValidEntry from ..models import Playbook @@ -17,7 +19,7 @@ def unzip_playbook(src, dist): fz.extract(file, dist) -class PlaybookViewSet(OrgBulkModelViewSet): +class PlaybookViewSet(CommonApiMixin, BulkModelViewSet): serializer_class = PlaybookSerializer permission_classes = () model = Playbook diff --git a/apps/ops/migrations/0028_auto_20221205_1627.py b/apps/ops/migrations/0028_auto_20221205_1627.py new file mode 100644 index 000000000..5b04102a7 --- /dev/null +++ b/apps/ops/migrations/0028_auto_20221205_1627.py @@ -0,0 +1,34 @@ +# Generated by Django 3.2.14 on 2022-12-05 08:27 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('ops', '0027_auto_20221024_1709'), + ] + + operations = [ + migrations.RenameField( + model_name='job', + old_name='owner', + new_name='creator', + ), + migrations.RemoveField( + model_name='adhoc', + name='org_id', + ), + migrations.RemoveField( + model_name='job', + name='org_id', + ), + migrations.RemoveField( + model_name='jobexecution', + name='org_id', + ), + migrations.RemoveField( + model_name='playbook', + name='org_id', + ), + ] diff --git a/apps/ops/models/adhoc.py b/apps/ops/models/adhoc.py index 890a47b91..d6fc27038 100644 --- a/apps/ops/models/adhoc.py +++ b/apps/ops/models/adhoc.py @@ -4,15 +4,15 @@ import uuid from django.db import models from django.utils.translation import ugettext_lazy as _ +from common.db.models import JMSBaseModel from common.utils import get_logger -from orgs.mixins.models import JMSOrgBaseModel __all__ = ["AdHoc"] logger = get_logger(__file__) -class AdHoc(JMSOrgBaseModel): +class AdHoc(JMSBaseModel): class Modules(models.TextChoices): shell = 'shell', _('Shell') winshell = 'win_shell', _('Powershell') @@ -26,7 +26,6 @@ class AdHoc(JMSOrgBaseModel): creator = models.ForeignKey('users.User', verbose_name=_("Creator"), on_delete=models.SET_NULL, null=True) comment = models.CharField(max_length=1024, default='', verbose_name=_('Comment'), null=True, blank=True) - @property def row_count(self): if len(self.args) == 0: diff --git a/apps/ops/models/job.py b/apps/ops/models/job.py index ff858a4fb..211d386d9 100644 --- a/apps/ops/models/job.py +++ b/apps/ops/models/job.py @@ -11,12 +11,12 @@ from celery import current_task __all__ = ["Job", "JobExecution"] +from common.db.models import JMSBaseModel from ops.ansible import JMSInventory, AdHocRunner, PlaybookRunner from ops.mixin import PeriodTaskModelMixin -from orgs.mixins.models import JMSOrgBaseModel -class Job(JMSOrgBaseModel, PeriodTaskModelMixin): +class Job(JMSBaseModel, PeriodTaskModelMixin): class Types(models.TextChoices): adhoc = 'adhoc', _('Adhoc') playbook = 'playbook', _('Playbook') @@ -40,7 +40,7 @@ class Job(JMSOrgBaseModel, PeriodTaskModelMixin): timeout = models.IntegerField(default=60, verbose_name=_('Timeout (Seconds)')) playbook = models.ForeignKey('ops.Playbook', verbose_name=_("Playbook"), null=True, on_delete=models.SET_NULL) type = models.CharField(max_length=128, choices=Types.choices, default=Types.adhoc, verbose_name=_("Type")) - owner = models.ForeignKey('users.User', verbose_name=_("Creator"), on_delete=models.SET_NULL, null=True) + creator = models.ForeignKey('users.User', verbose_name=_("Creator"), on_delete=models.SET_NULL, null=True) assets = models.ManyToManyField('assets.Asset', verbose_name=_("Assets")) runas = models.CharField(max_length=128, default='root', verbose_name=_('Runas')) runas_policy = models.CharField(max_length=128, choices=RunasPolicies.choices, default=RunasPolicies.skip, @@ -96,7 +96,7 @@ class Job(JMSOrgBaseModel, PeriodTaskModelMixin): ordering = ['date_created'] -class JobExecution(JMSOrgBaseModel): +class JobExecution(JMSBaseModel): id = models.UUIDField(default=uuid.uuid4, primary_key=True) task_id = models.UUIDField(null=True) status = models.CharField(max_length=16, verbose_name=_('Status'), default='running') diff --git a/apps/ops/models/playbook.py b/apps/ops/models/playbook.py index f92968762..59688f76d 100644 --- a/apps/ops/models/playbook.py +++ b/apps/ops/models/playbook.py @@ -5,11 +5,11 @@ from django.conf import settings from django.db import models from django.utils.translation import gettext_lazy as _ +from common.db.models import JMSBaseModel from ops.exception import PlaybookNoValidEntry -from orgs.mixins.models import JMSOrgBaseModel -class Playbook(JMSOrgBaseModel): +class Playbook(JMSBaseModel): id = models.UUIDField(default=uuid.uuid4, primary_key=True) name = models.CharField(max_length=128, verbose_name=_('Name'), null=True) path = models.FileField(upload_to='playbooks/') diff --git a/apps/ops/serializers/adhoc.py b/apps/ops/serializers/adhoc.py index 08d583be1..9883e104e 100644 --- a/apps/ops/serializers/adhoc.py +++ b/apps/ops/serializers/adhoc.py @@ -4,11 +4,10 @@ from __future__ import unicode_literals from rest_framework import serializers from common.drf.fields import ReadableHiddenField -from orgs.mixins.serializers import BulkOrgResourceModelSerializer from ..models import AdHoc -class AdHocSerializer(BulkOrgResourceModelSerializer, serializers.ModelSerializer): +class AdHocSerializer(serializers.ModelSerializer): creator = ReadableHiddenField(default=serializers.CurrentUserDefault()) row_count = serializers.IntegerField(read_only=True) size = serializers.IntegerField(read_only=True) diff --git a/apps/ops/serializers/job.py b/apps/ops/serializers/job.py index 1851cdfd8..05993dc08 100644 --- a/apps/ops/serializers/job.py +++ b/apps/ops/serializers/job.py @@ -3,11 +3,10 @@ from rest_framework import serializers from common.drf.fields import ReadableHiddenField from ops.mixin import PeriodTaskSerializerMixin from ops.models import Job, JobExecution -from orgs.mixins.serializers import BulkOrgResourceModelSerializer -class JobSerializer(BulkOrgResourceModelSerializer, PeriodTaskSerializerMixin): - owner = ReadableHiddenField(default=serializers.CurrentUserDefault()) +class JobSerializer(serializers.ModelSerializer, PeriodTaskSerializerMixin): + creator = ReadableHiddenField(default=serializers.CurrentUserDefault()) run_after_save = serializers.BooleanField(label=_("Run after save"), read_only=True, default=False, required=False) class Meta: @@ -15,7 +14,7 @@ class JobSerializer(BulkOrgResourceModelSerializer, PeriodTaskSerializerMixin): read_only_fields = ["id", "date_last_run", "date_created", "date_updated", "average_time_cost", "run_after_save"] fields = read_only_fields + [ - "name", "instant", "type", "module", "args", "playbook", "assets", "runas_policy", "runas", "owner", + "name", "instant", "type", "module", "args", "playbook", "assets", "runas_policy", "runas", "creator", "use_parameter_define", "parameters_define", "timeout", @@ -27,10 +26,12 @@ class JobSerializer(BulkOrgResourceModelSerializer, PeriodTaskSerializerMixin): class JobExecutionSerializer(serializers.ModelSerializer): + creator = ReadableHiddenField(default=serializers.CurrentUserDefault()) + class Meta: model = JobExecution read_only_fields = ["id", "task_id", "timedelta", "time_cost", 'is_finished', 'date_start', 'date_created', - 'is_success', 'task_id', 'short_id', 'job_type'] + 'is_success', 'task_id', 'short_id', 'job_type', 'creator'] fields = read_only_fields + [ "job", "parameters" ] diff --git a/apps/ops/serializers/playbook.py b/apps/ops/serializers/playbook.py index 1633688be..0334bdd45 100644 --- a/apps/ops/serializers/playbook.py +++ b/apps/ops/serializers/playbook.py @@ -12,7 +12,7 @@ def parse_playbook_name(path): return file_name.split(".")[-2] -class PlaybookSerializer(BulkOrgResourceModelSerializer, serializers.ModelSerializer): +class PlaybookSerializer(serializers.ModelSerializer): creator = ReadableHiddenField(default=serializers.CurrentUserDefault()) path = serializers.FileField(required=False)