Merge: v3 to dev (#9160)

* fix: 修改 ConnectionTokenSecretSerializer

* perf: connect token secret (#9155)

Co-authored-by: feng <1304903146@qq.com>
Co-authored-by: Jiangjie.Bai <bugatti_it@163.com>

* 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 <chenyang@fit2cloud.com>
pull/9161/head
Jiangjie.Bai 2022-12-06 11:03:14 +08:00 committed by GitHub
parent 9ef5f17d5e
commit 7842e3e5ab
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 284 additions and 243 deletions

View File

@ -3,6 +3,7 @@ from .platform import *
from .asset import * from .asset import *
from .label import Label from .label import Label
from .group import * from .group import *
from .gateway import *
from .domain import * from .domain import *
from .node import * from .node import *
from .utils import * from .utils import *

View File

@ -1,6 +1,7 @@
from assets.const import GATEWAY_NAME
from .common import Asset from .common import Asset
__all__ = ['Host']
class Host(Asset): class Host(Asset):
pass pass

View File

@ -50,6 +50,16 @@ class AbsConnectivity(models.Model):
abstract = True 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): class BaseAccount(JMSOrgBaseModel):
name = models.CharField(max_length=128, verbose_name=_("Name")) name = models.CharField(max_length=128, verbose_name=_("Name"))
username = models.CharField(max_length=128, blank=True, verbose_name=_('Username'), db_index=True) 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')) comment = models.TextField(blank=True, verbose_name=_('Comment'))
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'))
objects = BaseAccountManager.from_queryset(BaseAccountQuerySet)()
@property @property
def has_secret(self): def has_secret(self):
return bool(self.secret) return bool(self.secret)

View File

@ -2,21 +2,18 @@
# #
import uuid import uuid
import random import random
import socket
import paramiko
from django.db import models from django.db import models
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from common.utils import get_logger, lazyproperty from common.utils import get_logger, lazyproperty
from orgs.mixins.models import OrgModelMixin from orgs.mixins.models import OrgModelMixin
from assets.models import Host, Platform
from assets.const import GATEWAY_NAME, SecretType, Connectivity from .gateway import Gateway
from orgs.mixins.models import OrgManager
logger = get_logger(__file__) logger = get_logger(__file__)
__all__ = ['Domain', 'Gateway'] __all__ = ['Domain']
class Domain(OrgModelMixin): class Domain(OrgModelMixin):
@ -33,159 +30,26 @@ class Domain(OrgModelMixin):
def __str__(self): def __str__(self):
return self.name 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): def select_gateway(self):
return self.random_gateway() return self.random_gateway()
def random_gateway(self): def random_gateway(self):
gateways = [gw for gw in self.gateways if gw.is_connective] gateways = [gw for gw in self.active_gateways if gw.is_connective]
if gateways: if not gateways:
gateways = self.active_gateways
logger.warn(f'Gateway all bad. domain={self}, gateway_num={len(gateways)}.')
return random.choice(gateways) return random.choice(gateways)
logger.warn(f'Gateway all bad. domain={self}, gateway_num={len(self.gateways)}.') @lazyproperty
if self.gateways: def active_gateways(self):
return random.choice(self.gateways) return self.gateways.filter(is_active=True)
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
@lazyproperty @lazyproperty
def default_platform(self): def gateways(self):
return Platform.objects.get(name=GATEWAY_NAME, internal=True) return self.get_gateway_queryset().filter(domain=self)
def save(self, *args, **kwargs): @classmethod
platform = self.default_platform def get_gateway_queryset(cls):
self.platform_id = platform.id return Gateway.objects.all()
return super().save(*args, **kwargs)
@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
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
@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

View File

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

View File

@ -4,11 +4,11 @@
from .asset import * from .asset import *
from .label import * from .label import *
from .node import * from .node import *
from .gateway import *
from .domain import * from .domain import *
from .gathered_user import * from .gathered_user import *
from .favorite_asset import * from .favorite_asset import *
from .account import * from .account import *
from assets.serializers.account.backup import *
from .platform import * from .platform import *
from .cagegory import * from .cagegory import *
from .automations import * from .automations import *

View File

@ -1,2 +1,3 @@
from .account import * from .account import *
from .template import * from .template import *
from .backup import *

View File

@ -67,11 +67,11 @@ class AssetSerializer(OrgResourceSerializerMixin, WritableNestedModelSerializer)
labels = AssetLabelSerializer(many=True, required=False, label=_('Labels')) labels = AssetLabelSerializer(many=True, required=False, label=_('Labels'))
protocols = AssetProtocolsSerializer(many=True, required=False, label=_('Protocols')) protocols = AssetProtocolsSerializer(many=True, required=False, label=_('Protocols'))
accounts = AssetAccountSerializer(many=True, required=False, label=_('Accounts')) accounts = AssetAccountSerializer(many=True, required=False, label=_('Accounts'))
automation_enabled_info = serializers.SerializerMethodField() enabled_info = serializers.SerializerMethodField()
class Meta: class Meta:
model = Asset 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_small = fields_mini + ['is_active', 'comment']
fields_fk = ['domain', 'platform', 'platform'] fields_fk = ['domain', 'platform', 'platform']
fields_m2m = [ fields_m2m = [
@ -95,11 +95,15 @@ class AssetSerializer(OrgResourceSerializerMixin, WritableNestedModelSerializer)
return names return names
@staticmethod @staticmethod
def get_automation_enabled_info(obj): def get_enabled_info(obj):
automation = obj.platform.automation platform = obj.platform
automation = platform.automation
return { return {
'su_enabled': platform.su_enabled,
'ping_enabled': automation.ping_enabled, 'ping_enabled': automation.ping_enabled,
'domain_enabled': platform.domain_enabled,
'ansible_enabled': automation.ansible_enabled, 'ansible_enabled': automation.ansible_enabled,
'protocols_enabled': platform.protocols_enabled,
'gather_facts_enabled': automation.gather_facts_enabled, 'gather_facts_enabled': automation.gather_facts_enabled,
'push_account_enabled': automation.push_account_enabled, 'push_account_enabled': automation.push_account_enabled,
'change_secret_enabled': automation.change_secret_enabled, 'change_secret_enabled': automation.change_secret_enabled,

View File

@ -1,18 +1,20 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
from rest_framework import serializers
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from orgs.mixins.serializers import BulkOrgResourceModelSerializer from orgs.mixins.serializers import BulkOrgResourceModelSerializer
from common.drf.serializers import SecretReadableMixin
from common.drf.fields import ObjectRelatedField from common.drf.fields import ObjectRelatedField
from ..serializers import HostSerializer from ..serializers import GatewaySerializer
from ..models import Domain, Gateway, Asset from ..models import Domain, Asset
__all__ = ['DomainSerializer', 'DomainWithGatewaySerializer']
class DomainSerializer(BulkOrgResourceModelSerializer): class DomainSerializer(BulkOrgResourceModelSerializer):
asset_count = serializers.SerializerMethodField(label=_('Assets amount')) gateways = ObjectRelatedField(
gateway_count = serializers.SerializerMethodField(label=_('Gateways count')) many=True, required=False, queryset=Asset.objects, label=_('Gateway')
)
assets = ObjectRelatedField( assets = ObjectRelatedField(
many=True, required=False, queryset=Asset.objects, label=_('Asset') many=True, required=False, queryset=Asset.objects, label=_('Asset')
) )
@ -21,40 +23,10 @@ class DomainSerializer(BulkOrgResourceModelSerializer):
model = Domain model = Domain
fields_mini = ['id', 'name'] fields_mini = ['id', 'name']
fields_small = fields_mini + ['comment'] fields_small = fields_mini + ['comment']
fields_m2m = ['assets'] fields_m2m = ['assets', 'gateways']
read_only_fields = ['asset_count', 'gateway_count', 'date_created'] read_only_fields = ['date_created']
fields = fields_small + fields_m2m + read_only_fields fields = fields_small + fields_m2m + read_only_fields
extra_kwargs = {}
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
]
class DomainWithGatewaySerializer(BulkOrgResourceModelSerializer): class DomainWithGatewaySerializer(BulkOrgResourceModelSerializer):

View File

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

View File

@ -153,7 +153,7 @@ class ConnectionToken(OrgModelMixin, JMSBaseModel):
if not self.domain: if not self.domain:
return return
self.domain: Domain self.domain: Domain
return self.domain.random_gateway() return self.domain.select_gateway()
@lazyproperty @lazyproperty
def command_filter_acls(self): def command_filter_acls(self):
@ -161,7 +161,7 @@ class ConnectionToken(OrgModelMixin, JMSBaseModel):
kwargs = { kwargs = {
'user': self.user, 'user': self.user,
'asset': self.asset, 'asset': self.asset,
'account': self.account, 'account': self.account_object,
} }
acls = CommandFilterACL.filter_queryset(**kwargs).valid() acls = CommandFilterACL.filter_queryset(**kwargs).valid()
return acls return acls

View File

@ -1,13 +1,16 @@
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 acls.models import CommandGroup from common.drf.fields import ObjectRelatedField
from assets.models import Asset, Account, Platform from acls.models import CommandGroup, CommandFilterACL
from assets.models import Asset, Account, Platform, Gateway, Domain
from assets.serializers import PlatformSerializer, AssetProtocolsSerializer 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 users.models import User
from perms.serializers.permission import ActionChoicesField
from orgs.mixins.serializers import OrgResourceModelSerializerMixin
from ..models import ConnectionToken
__all__ = [ __all__ = [
'ConnectionTokenSecretSerializer', 'ConnectionTokenSecretSerializer',
@ -53,21 +56,33 @@ class _ConnectionTokenAccountSerializer(serializers.ModelSerializer):
class _ConnectionTokenGatewaySerializer(serializers.ModelSerializer): class _ConnectionTokenGatewaySerializer(serializers.ModelSerializer):
""" Gateway """ """ 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: class Meta:
model = Asset model = Gateway
fields = [ fields = [
'id', 'address', 'port', 'id', 'name', 'address', 'protocols', 'account'
# 'username', 'password', 'private_key'
] ]
class _ConnectionTokenACLCmdGroupSerializer(serializers.ModelSerializer): class _ConnectionTokenCommandFilterACLSerializer(serializers.ModelSerializer):
""" ACL command group""" 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: class Meta:
model = CommandGroup model = CommandFilterACL
fields = [ 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') account = _ConnectionTokenAccountSerializer(read_only=True, source='account_object')
gateway = _ConnectionTokenGatewaySerializer(read_only=True) gateway = _ConnectionTokenGatewaySerializer(read_only=True)
platform = _ConnectionTokenPlatformSerializer(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() actions = ActionChoicesField()
expire_at = serializers.IntegerField() expire_at = serializers.IntegerField()
expire_now = serializers.BooleanField(label=_('Expired now'), write_only=True, default=True) expire_now = serializers.BooleanField(label=_('Expired now'), write_only=True, default=True)
@ -97,8 +113,8 @@ class ConnectionTokenSecretSerializer(OrgResourceModelSerializerMixin):
model = ConnectionToken model = ConnectionToken
fields = [ fields = [
'id', 'value', 'user', 'asset', 'account', 'id', 'value', 'user', 'asset', 'account',
'platform', 'acl_command_groups', 'protocol', 'platform', 'command_filter_acls', 'protocol',
'gateway', 'actions', 'expire_at', 'expire_now', 'domain', 'gateway', 'actions', 'expire_at', 'expire_now',
'connect_method' 'connect_method'
] ]
extra_kwargs = { extra_kwargs = {

View File

@ -1,9 +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 authentication.models import ConnectionToken
from orgs.mixins.serializers import OrgResourceModelSerializerMixin from orgs.mixins.serializers import OrgResourceModelSerializerMixin
from ..models import ConnectionToken
__all__ = [ __all__ = [
'ConnectionTokenSerializer', 'SuperConnectionTokenSerializer', 'ConnectionTokenSerializer', 'SuperConnectionTokenSerializer',
] ]

View File

@ -1,9 +1,9 @@
# -*- coding: utf-8 -*- # -*- 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 ..models import AdHoc
from ..serializers import ( from ..serializers import (
AdHocSerializer AdHocSerializer
@ -14,7 +14,9 @@ __all__ = [
] ]
class AdHocViewSet(OrgBulkModelViewSet): class AdHocViewSet(CommonApiMixin, BulkModelViewSet):
serializer_class = AdHocSerializer serializer_class = AdHocSerializer
permission_classes = () permission_classes = ()
model = AdHoc
def get_queryset(self):
return AdHoc.objects.filter(creator=self.request.user)

View File

@ -1,12 +1,13 @@
from rest_framework import viewsets from rest_framework import viewsets
from rest_framework_bulk import BulkModelViewSet
from common.mixins import CommonApiMixin
from ops.models import Job, JobExecution from ops.models import Job, JobExecution
from ops.serializers.job import JobSerializer, JobExecutionSerializer from ops.serializers.job import JobSerializer, JobExecutionSerializer
__all__ = ['JobViewSet', 'JobExecutionViewSet'] __all__ = ['JobViewSet', 'JobExecutionViewSet']
from ops.tasks import run_ops_job_execution from ops.tasks import run_ops_job_execution
from orgs.mixins.api import OrgBulkModelViewSet
def set_task_to_serializer_data(serializer, task): def set_task_to_serializer_data(serializer, task):
@ -15,13 +16,12 @@ def set_task_to_serializer_data(serializer, task):
setattr(serializer, "_data", data) setattr(serializer, "_data", data)
class JobViewSet(OrgBulkModelViewSet): class JobViewSet(CommonApiMixin, BulkModelViewSet):
serializer_class = JobSerializer serializer_class = JobSerializer
model = Job
permission_classes = () permission_classes = ()
def get_queryset(self): def get_queryset(self):
query_set = super().get_queryset() query_set = Job.objects.filter(creator=self.request.user)
if self.action != 'retrieve': if self.action != 'retrieve':
return query_set.filter(instant=False) return query_set.filter(instant=False)
return query_set return query_set
@ -45,11 +45,10 @@ class JobViewSet(OrgBulkModelViewSet):
set_task_to_serializer_data(serializer, task) set_task_to_serializer_data(serializer, task)
class JobExecutionViewSet(OrgBulkModelViewSet): class JobExecutionViewSet(CommonApiMixin, BulkModelViewSet):
serializer_class = JobExecutionSerializer serializer_class = JobExecutionSerializer
http_method_names = ('get', 'post', 'head', 'options',) http_method_names = ('get', 'post', 'head', 'options',)
permission_classes = () permission_classes = ()
model = JobExecution
def perform_create(self, serializer): def perform_create(self, serializer):
instance = serializer.save() instance = serializer.save()
@ -57,7 +56,8 @@ class JobExecutionViewSet(OrgBulkModelViewSet):
set_task_to_serializer_data(serializer, task) set_task_to_serializer_data(serializer, task)
def get_queryset(self): 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') job_id = self.request.query_params.get('job_id')
if job_id: if job_id:
query_set = query_set.filter(job_id=job_id) query_set = query_set.filter(job_id=job_id)

View File

@ -2,7 +2,9 @@ import os
import zipfile import zipfile
from django.conf import settings from django.conf import settings
from rest_framework_bulk import BulkModelViewSet
from common.mixins import CommonApiMixin
from orgs.mixins.api import OrgBulkModelViewSet from orgs.mixins.api import OrgBulkModelViewSet
from ..exception import PlaybookNoValidEntry from ..exception import PlaybookNoValidEntry
from ..models import Playbook from ..models import Playbook
@ -17,7 +19,7 @@ def unzip_playbook(src, dist):
fz.extract(file, dist) fz.extract(file, dist)
class PlaybookViewSet(OrgBulkModelViewSet): class PlaybookViewSet(CommonApiMixin, BulkModelViewSet):
serializer_class = PlaybookSerializer serializer_class = PlaybookSerializer
permission_classes = () permission_classes = ()
model = Playbook model = Playbook

View File

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

View File

@ -4,15 +4,15 @@ import uuid
from django.db import models from django.db import models
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from common.db.models import JMSBaseModel
from common.utils import get_logger from common.utils import get_logger
from orgs.mixins.models import JMSOrgBaseModel
__all__ = ["AdHoc"] __all__ = ["AdHoc"]
logger = get_logger(__file__) logger = get_logger(__file__)
class AdHoc(JMSOrgBaseModel): class AdHoc(JMSBaseModel):
class Modules(models.TextChoices): class Modules(models.TextChoices):
shell = 'shell', _('Shell') shell = 'shell', _('Shell')
winshell = 'win_shell', _('Powershell') 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) 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) comment = models.CharField(max_length=1024, default='', verbose_name=_('Comment'), null=True, blank=True)
@property @property
def row_count(self): def row_count(self):
if len(self.args) == 0: if len(self.args) == 0:

View File

@ -11,12 +11,12 @@ from celery import current_task
__all__ = ["Job", "JobExecution"] __all__ = ["Job", "JobExecution"]
from common.db.models import JMSBaseModel
from ops.ansible import JMSInventory, AdHocRunner, PlaybookRunner from ops.ansible import JMSInventory, AdHocRunner, PlaybookRunner
from ops.mixin import PeriodTaskModelMixin from ops.mixin import PeriodTaskModelMixin
from orgs.mixins.models import JMSOrgBaseModel
class Job(JMSOrgBaseModel, PeriodTaskModelMixin): class Job(JMSBaseModel, PeriodTaskModelMixin):
class Types(models.TextChoices): class Types(models.TextChoices):
adhoc = 'adhoc', _('Adhoc') adhoc = 'adhoc', _('Adhoc')
playbook = 'playbook', _('Playbook') playbook = 'playbook', _('Playbook')
@ -40,7 +40,7 @@ class Job(JMSOrgBaseModel, PeriodTaskModelMixin):
timeout = models.IntegerField(default=60, verbose_name=_('Timeout (Seconds)')) timeout = models.IntegerField(default=60, verbose_name=_('Timeout (Seconds)'))
playbook = models.ForeignKey('ops.Playbook', verbose_name=_("Playbook"), null=True, on_delete=models.SET_NULL) 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")) 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")) assets = models.ManyToManyField('assets.Asset', verbose_name=_("Assets"))
runas = models.CharField(max_length=128, default='root', verbose_name=_('Runas')) runas = models.CharField(max_length=128, default='root', verbose_name=_('Runas'))
runas_policy = models.CharField(max_length=128, choices=RunasPolicies.choices, default=RunasPolicies.skip, runas_policy = models.CharField(max_length=128, choices=RunasPolicies.choices, default=RunasPolicies.skip,
@ -96,7 +96,7 @@ class Job(JMSOrgBaseModel, PeriodTaskModelMixin):
ordering = ['date_created'] ordering = ['date_created']
class JobExecution(JMSOrgBaseModel): class JobExecution(JMSBaseModel):
id = models.UUIDField(default=uuid.uuid4, primary_key=True) id = models.UUIDField(default=uuid.uuid4, primary_key=True)
task_id = models.UUIDField(null=True) task_id = models.UUIDField(null=True)
status = models.CharField(max_length=16, verbose_name=_('Status'), default='running') status = models.CharField(max_length=16, verbose_name=_('Status'), default='running')

View File

@ -5,11 +5,11 @@ from django.conf import settings
from django.db import models from django.db import models
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from common.db.models import JMSBaseModel
from ops.exception import PlaybookNoValidEntry 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) id = models.UUIDField(default=uuid.uuid4, primary_key=True)
name = models.CharField(max_length=128, verbose_name=_('Name'), null=True) name = models.CharField(max_length=128, verbose_name=_('Name'), null=True)
path = models.FileField(upload_to='playbooks/') path = models.FileField(upload_to='playbooks/')

View File

@ -4,11 +4,10 @@ from __future__ import unicode_literals
from rest_framework import serializers from rest_framework import serializers
from common.drf.fields import ReadableHiddenField from common.drf.fields import ReadableHiddenField
from orgs.mixins.serializers import BulkOrgResourceModelSerializer
from ..models import AdHoc from ..models import AdHoc
class AdHocSerializer(BulkOrgResourceModelSerializer, serializers.ModelSerializer): class AdHocSerializer(serializers.ModelSerializer):
creator = ReadableHiddenField(default=serializers.CurrentUserDefault()) creator = ReadableHiddenField(default=serializers.CurrentUserDefault())
row_count = serializers.IntegerField(read_only=True) row_count = serializers.IntegerField(read_only=True)
size = serializers.IntegerField(read_only=True) size = serializers.IntegerField(read_only=True)

View File

@ -3,11 +3,10 @@ from rest_framework import serializers
from common.drf.fields import ReadableHiddenField from common.drf.fields import ReadableHiddenField
from ops.mixin import PeriodTaskSerializerMixin from ops.mixin import PeriodTaskSerializerMixin
from ops.models import Job, JobExecution from ops.models import Job, JobExecution
from orgs.mixins.serializers import BulkOrgResourceModelSerializer
class JobSerializer(BulkOrgResourceModelSerializer, PeriodTaskSerializerMixin): class JobSerializer(serializers.ModelSerializer, PeriodTaskSerializerMixin):
owner = ReadableHiddenField(default=serializers.CurrentUserDefault()) creator = ReadableHiddenField(default=serializers.CurrentUserDefault())
run_after_save = serializers.BooleanField(label=_("Run after save"), read_only=True, default=False, required=False) run_after_save = serializers.BooleanField(label=_("Run after save"), read_only=True, default=False, required=False)
class Meta: class Meta:
@ -15,7 +14,7 @@ class JobSerializer(BulkOrgResourceModelSerializer, PeriodTaskSerializerMixin):
read_only_fields = ["id", "date_last_run", "date_created", "date_updated", "average_time_cost", read_only_fields = ["id", "date_last_run", "date_created", "date_updated", "average_time_cost",
"run_after_save"] "run_after_save"]
fields = read_only_fields + [ 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", "use_parameter_define",
"parameters_define", "parameters_define",
"timeout", "timeout",
@ -27,10 +26,12 @@ class JobSerializer(BulkOrgResourceModelSerializer, PeriodTaskSerializerMixin):
class JobExecutionSerializer(serializers.ModelSerializer): class JobExecutionSerializer(serializers.ModelSerializer):
creator = ReadableHiddenField(default=serializers.CurrentUserDefault())
class Meta: class Meta:
model = JobExecution model = JobExecution
read_only_fields = ["id", "task_id", "timedelta", "time_cost", 'is_finished', 'date_start', 'date_created', 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 + [ fields = read_only_fields + [
"job", "parameters" "job", "parameters"
] ]

View File

@ -12,7 +12,7 @@ def parse_playbook_name(path):
return file_name.split(".")[-2] return file_name.split(".")[-2]
class PlaybookSerializer(BulkOrgResourceModelSerializer, serializers.ModelSerializer): class PlaybookSerializer(serializers.ModelSerializer):
creator = ReadableHiddenField(default=serializers.CurrentUserDefault()) creator = ReadableHiddenField(default=serializers.CurrentUserDefault())
path = serializers.FileField(required=False) path = serializers.FileField(required=False)