:wqMerge branch 'v3' of github.com:jumpserver/jumpserver into v3
This commit is contained in:
ibuler 2022-12-27 18:26:24 +08:00
commit 1b1c91bab0
29 changed files with 147 additions and 154 deletions

View File

@ -4,7 +4,6 @@ from .automations import *
from .category import * from .category import *
from .domain import * from .domain import *
from .favorite_asset import * from .favorite_asset import *
from .gathered_user import *
from .label import * from .label import *
from .mixin import * from .mixin import *
from .node import * from .node import *

View File

@ -1,20 +0,0 @@
# -*- coding: utf-8 -*-
#
from orgs.mixins.api import OrgModelViewSet
from assets.models import GatheredUser
from ..serializers import GatheredUserSerializer
from ..filters import AssetRelatedByNodeFilterBackend
__all__ = ['GatheredUserViewSet']
class GatheredUserViewSet(OrgModelViewSet):
model = GatheredUser
serializer_class = GatheredUserSerializer
extra_filter_backends = [AssetRelatedByNodeFilterBackend]
filterset_fields = ['asset', 'username', 'present', 'asset__address', 'asset__name', 'asset_id']
search_fields = ['username', 'asset__address', 'asset__name']

View File

@ -1,8 +1,9 @@
from django.utils.translation import ugettext_lazy as _
from common.utils import get_logger from common.utils import get_logger
from assets.const import AutomationTypes from assets.const import AutomationTypes, Source
from orgs.utils import tmp_to_org from orgs.utils import tmp_to_org
from .filter import GatherAccountsFilter from .filter import GatherAccountsFilter
from ...models import GatheredUser
from ..base.manager import BasePlaybookManager from ..base.manager import BasePlaybookManager
logger = get_logger(__name__) logger = get_logger(__name__)
@ -26,20 +27,33 @@ class GatherAccountsManager(BasePlaybookManager):
result = GatherAccountsFilter(host).run(self.method_id_meta_mapper, result) result = GatherAccountsFilter(host).run(self.method_id_meta_mapper, result)
return result return result
@staticmethod
def bulk_create_accounts(asset, result):
account_objs = []
account_model = asset.accounts.model
account_usernames = set(asset.accounts.values_list('username', flat=True))
with tmp_to_org(asset.org_id):
accounts_dict = {}
for username, data in result.items():
comment = ''
d = {'asset': asset, 'username': username, 'name': username, 'source': Source.COLLECTED}
if data.get('date'):
comment += f"{_('Date last login')}: {data['date']}\n "
if data.get('address'):
comment += f"{_('IP last login')}: {data['address'][:32]}"
d['comment'] = comment
accounts_dict[username] = d
for username, data in accounts_dict.items():
if username in account_usernames:
continue
account_objs.append(account_model(**data))
account_model.objects.bulk_create(account_objs)
def on_host_success(self, host, result): def on_host_success(self, host, result):
info = result.get('debug', {}).get('res', {}).get('info', {}) info = result.get('debug', {}).get('res', {}).get('info', {})
asset = self.host_asset_mapper.get(host) asset = self.host_asset_mapper.get(host)
org_id = asset.org_id
if asset and info: if asset and info:
result = self.filter_success_result(host, info) result = self.filter_success_result(host, info)
with tmp_to_org(org_id): self.bulk_create_accounts(asset, result)
GatheredUser.objects.filter(asset=asset, present=True).update(present=False)
for username, data in result.items():
defaults = {'asset': asset, 'present': True, 'username': username}
if data.get('date'):
defaults['date_last_login'] = data['date']
if data.get('address'):
defaults['ip_last_login'] = data['address'][:32]
GatheredUser.objects.update_or_create(defaults=defaults, asset=asset, username=username)
else: else:
logger.error("Not found info".format(host)) logger.error("Not found info".format(host))

View File

@ -13,3 +13,14 @@ class SecretType(TextChoices):
SSH_KEY = 'ssh_key', _('SSH key') SSH_KEY = 'ssh_key', _('SSH key')
ACCESS_KEY = 'access_key', _('Access key') ACCESS_KEY = 'access_key', _('Access key')
TOKEN = 'token', _('Token') TOKEN = 'token', _('Token')
class AliasAccount(TextChoices):
ALL = '@ALL', _('All')
INPUT = '@INPUT', _('Manual input')
USER = '@USER', _('Dynamic user')
class Source(TextChoices):
LOCAL = 'local', _('Local')
COLLECTED = 'collected', _('Collected')

View File

@ -126,17 +126,6 @@ class LabelFilterBackend(filters.BaseFilterBackend):
return queryset return queryset
class AssetRelatedByNodeFilterBackend(AssetByNodeFilterBackend):
def filter_node_related_all(self, queryset, node):
return queryset.filter(
Q(asset__nodes__key__istartswith=f'{node.key}:') |
Q(asset__nodes__key=node.key)
).distinct()
def filter_node_related_direct(self, queryset, node):
return queryset.filter(asset__nodes__key=node.key).distinct()
class IpInFilterBackend(filters.BaseFilterBackend): class IpInFilterBackend(filters.BaseFilterBackend):
def filter_queryset(self, request, queryset, view): def filter_queryset(self, request, queryset, view):
ips = request.query_params.get('ips') ips = request.query_params.get('ips')

View File

@ -0,0 +1,21 @@
# Generated by Django 3.2.16 on 2022-12-27 09:40
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('assets', '0118_auto_20221227_1504'),
]
operations = [
migrations.AddField(
model_name='account',
name='source',
field=models.CharField(default='local', max_length=30, verbose_name='Source'),
),
migrations.DeleteModel(
name='GatheredUser',
),
]

View File

@ -7,7 +7,6 @@ from .gateway import *
from .domain import * from .domain import *
from .node import * from .node import *
from .utils import * from .utils import *
from .gathered_user import *
from .favorite_asset import * from .favorite_asset import *
from .account import * from .account import *
from .backup import * from .backup import *

View File

@ -3,6 +3,7 @@ from django.utils.translation import gettext_lazy as _
from simple_history.models import HistoricalRecords from simple_history.models import HistoricalRecords
from common.utils import lazyproperty from common.utils import lazyproperty
from ..const import AliasAccount, Source
from .base import AbsConnectivity, BaseAccount from .base import AbsConnectivity, BaseAccount
__all__ = ['Account', 'AccountTemplate'] __all__ = ['Account', 'AccountTemplate']
@ -40,11 +41,6 @@ class AccountHistoricalRecords(HistoricalRecords):
class Account(AbsConnectivity, BaseAccount): class Account(AbsConnectivity, BaseAccount):
class AliasAccount(models.TextChoices):
ALL = '@ALL', _('All')
INPUT = '@INPUT', _('Manual input')
USER = '@USER', _('Dynamic user')
asset = models.ForeignKey( asset = models.ForeignKey(
'assets.Asset', related_name='accounts', 'assets.Asset', related_name='accounts',
on_delete=models.CASCADE, verbose_name=_('Asset') on_delete=models.CASCADE, verbose_name=_('Asset')
@ -55,6 +51,7 @@ class Account(AbsConnectivity, BaseAccount):
) )
version = models.IntegerField(default=0, verbose_name=_('Version')) version = models.IntegerField(default=0, verbose_name=_('Version'))
history = AccountHistoricalRecords(included_fields=['id', 'secret', 'secret_type', 'version']) history = AccountHistoricalRecords(included_fields=['id', 'secret', 'secret_type', 'version'])
source = models.CharField(max_length=30, default=Source.LOCAL, verbose_name=_('Source'))
class Meta: class Meta:
verbose_name = _('Account') verbose_name = _('Account')
@ -89,12 +86,12 @@ class Account(AbsConnectivity, BaseAccount):
@classmethod @classmethod
def get_manual_account(cls): def get_manual_account(cls):
""" @INPUT 手动登录的账号(any) """ """ @INPUT 手动登录的账号(any) """
return cls(name=cls.AliasAccount.INPUT.label, username=cls.AliasAccount.INPUT.value, secret=None) return cls(name=AliasAccount.INPUT.label, username=AliasAccount.INPUT.value, secret=None)
@classmethod @classmethod
def get_user_account(cls, username): def get_user_account(cls, username):
""" @USER 动态用户的账号(self) """ """ @USER 动态用户的账号(self) """
return cls(name=cls.AliasAccount.USER.label, username=cls.AliasAccount.USER.value) return cls(name=AliasAccount.USER.label, username=AliasAccount.USER.value)
def get_su_from_accounts(self): def get_su_from_accounts(self):
""" 排除自己和以自己为 su-from 的账号 """ """ 排除自己和以自己为 su-from 的账号 """

View File

@ -1,31 +0,0 @@
# -*- coding: utf-8 -*-
#
from django.db import models
from django.utils.translation import ugettext_lazy as _
from orgs.mixins.models import JMSOrgBaseModel
__all__ = ['GatheredUser']
class GatheredUser(JMSOrgBaseModel):
asset = models.ForeignKey('assets.Asset', on_delete=models.CASCADE, verbose_name=_("Asset"))
username = models.CharField(max_length=32, blank=True, db_index=True, verbose_name=_('Username'))
present = models.BooleanField(default=True, verbose_name=_("Present"))
date_last_login = models.DateTimeField(null=True, verbose_name=_("Date last login"))
ip_last_login = models.CharField(max_length=39, default='', verbose_name=_("IP last login"))
@property
def name(self):
return self.asset.name
@property
def ip(self):
return self.asset.address
class Meta:
verbose_name = _('GatherUser')
ordering = ['asset']
def __str__(self):
return '{}: {}'.format(self.asset.name, self.username)

View File

@ -6,7 +6,6 @@ from .label import *
from .node import * from .node import *
from .gateway import * from .gateway import *
from .domain import * from .domain import *
from .gathered_user import *
from .favorite_asset import * from .favorite_asset import *
from .account import * from .account import *
from .platform import * from .platform import *

View File

@ -1,7 +1,7 @@
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 assets.const import SecretType from assets.const import SecretType, Source
from assets.models import Account, AccountTemplate, Asset from assets.models import Account, AccountTemplate, Asset
from assets.tasks import push_accounts_to_assets from assets.tasks import push_accounts_to_assets
from common.drf.fields import ObjectRelatedField, LabeledChoiceField from common.drf.fields import ObjectRelatedField, LabeledChoiceField
@ -74,6 +74,7 @@ class AccountAssetSerializer(serializers.ModelSerializer):
class AccountSerializer(AccountSerializerCreateMixin, BaseAccountSerializer): class AccountSerializer(AccountSerializerCreateMixin, BaseAccountSerializer):
asset = AccountAssetSerializer(label=_('Asset')) asset = AccountAssetSerializer(label=_('Asset'))
source = LabeledChoiceField(choices=Source.choices, label=_("Source"), read_only=True)
su_from = ObjectRelatedField( su_from = ObjectRelatedField(
required=False, queryset=Account.objects, allow_null=True, allow_empty=True, required=False, queryset=Account.objects, allow_null=True, allow_empty=True,
label=_('Su from'), attrs=('id', 'name', 'username') label=_('Su from'), attrs=('id', 'name', 'username')
@ -83,7 +84,7 @@ class AccountSerializer(AccountSerializerCreateMixin, BaseAccountSerializer):
model = Account model = Account
fields = BaseAccountSerializer.Meta.fields \ fields = BaseAccountSerializer.Meta.fields \
+ ['su_from', 'version', 'asset'] \ + ['su_from', 'version', 'asset'] \
+ ['template', 'push_now'] + ['template', 'push_now', 'source']
extra_kwargs = { extra_kwargs = {
**BaseAccountSerializer.Meta.extra_kwargs, **BaseAccountSerializer.Meta.extra_kwargs,
'name': {'required': False, 'allow_null': True}, 'name': {'required': False, 'allow_null': True},

View File

@ -67,6 +67,7 @@ class AssetSerializer(BulkOrgResourceSerializerMixin, WritableNestedModelSeriali
nodes = ObjectRelatedField(many=True, required=False, queryset=Node.objects, label=_('Nodes')) nodes = ObjectRelatedField(many=True, required=False, queryset=Node.objects, label=_('Nodes'))
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=_('Account'))
class Meta: class Meta:
model = Asset model = Asset
@ -85,6 +86,7 @@ class AssetSerializer(BulkOrgResourceSerializerMixin, WritableNestedModelSeriali
extra_kwargs = { extra_kwargs = {
'name': {'label': _("Name")}, 'name': {'label': _("Name")},
'address': {'label': _('Address')}, 'address': {'label': _('Address')},
'nodes_display': {'label': _('Node path')},
} }
@classmethod @classmethod

View File

@ -1,26 +0,0 @@
# -*- coding: utf-8 -*-
#
from django.utils.translation import ugettext_lazy as _
from orgs.mixins.serializers import OrgResourceModelSerializerMixin
from common.drf.fields import ObjectRelatedField
from ..models import GatheredUser, Asset
class GatheredUserSerializer(OrgResourceModelSerializerMixin):
asset = ObjectRelatedField(queryset=Asset.objects, label=_('Asset'))
class Meta:
model = GatheredUser
fields_mini = ['id']
fields_small = fields_mini + [
'username', 'ip_last_login', 'present', 'name',
'date_last_login', 'date_created', 'date_updated'
]
fields_fk = ['asset', 'ip']
fields = fields_small + fields_fk
read_only_fields = fields
extra_kwargs = {
'name': {'label': _("Hostname")},
'ip': {'label': 'IP'},
}

View File

@ -23,7 +23,6 @@ router.register(r'labels', api.LabelViewSet, 'label')
router.register(r'nodes', api.NodeViewSet, 'node') router.register(r'nodes', api.NodeViewSet, 'node')
router.register(r'domains', api.DomainViewSet, 'domain') router.register(r'domains', api.DomainViewSet, 'domain')
router.register(r'gateways', api.GatewayViewSet, 'gateway') router.register(r'gateways', api.GatewayViewSet, 'gateway')
router.register(r'gathered-users', api.GatheredUserViewSet, 'gathered-user')
router.register(r'favorite-assets', api.FavoriteAssetViewSet, 'favorite-asset') router.register(r'favorite-assets', api.FavoriteAssetViewSet, 'favorite-asset')
router.register(r'account-backup-plans', api.AccountBackupPlanViewSet, 'account-backup') router.register(r'account-backup-plans', api.AccountBackupPlanViewSet, 'account-backup')
router.register(r'account-backup-plan-executions', api.AccountBackupPlanExecutionViewSet, 'account-backup-execution') router.register(r'account-backup-plan-executions', api.AccountBackupPlanExecutionViewSet, 'account-backup-execution')
@ -51,7 +50,6 @@ urlpatterns = [
name='account-secret-history'), name='account-secret-history'),
path('nodes/category/tree/', api.CategoryTreeApi.as_view(), name='asset-category-tree'), path('nodes/category/tree/', api.CategoryTreeApi.as_view(), name='asset-category-tree'),
# path('nodes/tree/', api.NodeListAsTreeApi.as_view(), name='node-tree'),
path('nodes/children/tree/', api.NodeChildrenAsTreeApi.as_view(), name='node-children-tree'), path('nodes/children/tree/', api.NodeChildrenAsTreeApi.as_view(), name='node-children-tree'),
path('nodes/<uuid:pk>/children/', api.NodeChildrenApi.as_view(), name='node-children'), path('nodes/<uuid:pk>/children/', api.NodeChildrenApi.as_view(), name='node-children'),
path('nodes/children/', api.NodeChildrenApi.as_view(), name='node-children-2'), path('nodes/children/', api.NodeChildrenApi.as_view(), name='node-children-2'),

View File

@ -17,12 +17,10 @@ MODELS_NEED_RECORD = (
"LoginAssetACL", "LoginAssetACL",
"LoginConfirmSetting", "LoginConfirmSetting",
# assets # assets
'Asset', 'Node', 'AdminUser', 'SystemUser', 'Domain', 'Gateway', 'CommandFilterRule', 'Asset', 'Node', 'Domain', 'Gateway', 'CommandFilterRule',
'CommandFilter', 'Platform', 'Label', 'CommandFilter', 'Platform', 'Label',
# applications
'Application',
# account # account
'AuthBook', 'Account',
# orgs # orgs
"Organization", "Organization",
# settings # settings
@ -36,8 +34,7 @@ MODELS_NEED_RECORD = (
# rbac # rbac
'Role', 'SystemRole', 'OrgRole', 'RoleBinding', 'OrgRoleBinding', 'SystemRoleBinding', 'Role', 'SystemRole', 'OrgRole', 'RoleBinding', 'OrgRoleBinding', 'SystemRoleBinding',
# xpack # xpack
'License', 'Account', 'SyncInstanceTask', 'ChangeAuthPlan', 'License', 'Account', 'SyncInstanceTask', 'Interface',
'GatherUserTask', 'Interface',
) )

View File

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:a533ee3b36cdd61fe239be159cc80362e5cd358f134857987029bde8e4c1231e oid sha256:93524bfafbdfcb59e20920619c934b1df7b040573e98d1f1f481c73126110630
size 119530 size 119578

View File

@ -8,7 +8,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2022-12-26 20:28+0800\n" "POT-Creation-Date: 2022-12-27 12:11+0800\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
@ -1308,12 +1308,16 @@ msgstr "プロトコル"
msgid "Address" msgid "Address"
msgstr "アドレス" msgstr "アドレス"
#: assets/serializers/asset/common.py:156 #: assets/serializers/asset/common.py:89
msgid "Node path"
msgstr "ノードパスです"
#: assets/serializers/asset/common.py:157
#, fuzzy #, fuzzy
msgid "Platform not exist" msgid "Platform not exist"
msgstr "アプリが存在しません" msgstr "アプリが存在しません"
#: assets/serializers/asset/common.py:172 #: assets/serializers/asset/common.py:173
#, fuzzy #, fuzzy
msgid "Protocol is required: {}" msgid "Protocol is required: {}"
msgstr "プロトコル重複: {}" msgstr "プロトコル重複: {}"
@ -7443,9 +7447,6 @@ msgstr "コミュニティ版"
#~ msgid "Apps amount" #~ msgid "Apps amount"
#~ msgstr "アプリの量" #~ msgstr "アプリの量"
#~ msgid "Nodes amount"
#~ msgstr "ノード量"
#~ msgid "Login mode display" #~ msgid "Login mode display"
#~ msgstr "ログインモード表示" #~ msgstr "ログインモード表示"

View File

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:7496f27202b5dcd9d677a70f9efc86c30b74fc306d6b5fa7202238c15c845971 oid sha256:8ea4225734c8f9df88bb72379ccd48b50d833766fbde3ce64cacf5eb6f87082b
size 105814 size 105853

View File

@ -7,7 +7,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: JumpServer 0.3.3\n" "Project-Id-Version: JumpServer 0.3.3\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2022-12-26 20:28+0800\n" "POT-Creation-Date: 2022-12-27 12:11+0800\n"
"PO-Revision-Date: 2021-05-20 10:54+0800\n" "PO-Revision-Date: 2021-05-20 10:54+0800\n"
"Last-Translator: ibuler <ibuler@qq.com>\n" "Last-Translator: ibuler <ibuler@qq.com>\n"
"Language-Team: JumpServer team<ibuler@qq.com>\n" "Language-Team: JumpServer team<ibuler@qq.com>\n"
@ -1246,11 +1246,15 @@ msgstr "协议组"
msgid "Address" msgid "Address"
msgstr "地址" msgstr "地址"
#: assets/serializers/asset/common.py:156 #: assets/serializers/asset/common.py:89
msgid "Node path"
msgstr "节点路径"
#: assets/serializers/asset/common.py:157
msgid "Platform not exist" msgid "Platform not exist"
msgstr "平台不存在" msgstr "平台不存在"
#: assets/serializers/asset/common.py:172 #: assets/serializers/asset/common.py:173
msgid "Protocol is required: {}" msgid "Protocol is required: {}"
msgstr "协议是必填的: {}" msgstr "协议是必填的: {}"
@ -7217,9 +7221,6 @@ msgstr "社区版"
#~ msgid "Apps amount" #~ msgid "Apps amount"
#~ msgstr "应用数量" #~ msgstr "应用数量"
#~ msgid "Nodes amount"
#~ msgstr "节点数量"
#~ msgid "Login mode display" #~ msgid "Login mode display"
#~ msgstr "认证方式名称" #~ msgstr "认证方式名称"

View File

@ -8,6 +8,7 @@ class DefaultCallback:
'failed': 'failed', 'failed': 'failed',
'running': 'running', 'running': 'running',
'pending': 'pending', 'pending': 'pending',
'timeout': 'timeout',
'unknown': 'unknown' 'unknown': 'unknown'
} }

View File

@ -14,7 +14,7 @@ class AdHocRunner:
] ]
def __init__(self, inventory, module, module_args='', pattern='*', project_dir='/tmp/', extra_vars={}, def __init__(self, inventory, module, module_args='', pattern='*', project_dir='/tmp/', extra_vars={},
dry_run=False): dry_run=False, timeout=-1):
self.id = uuid.uuid4() self.id = uuid.uuid4()
self.inventory = inventory self.inventory = inventory
self.pattern = pattern self.pattern = pattern
@ -25,6 +25,7 @@ class AdHocRunner:
self.runner = None self.runner = None
self.extra_vars = extra_vars self.extra_vars = extra_vars
self.dry_run = dry_run self.dry_run = dry_run
self.timeout = timeout
def check_module(self): def check_module(self):
if self.module not in self.cmd_modules_choices: if self.module not in self.cmd_modules_choices:
@ -41,6 +42,7 @@ class AdHocRunner:
os.mkdir(self.project_dir, 0o755) os.mkdir(self.project_dir, 0o755)
ansible_runner.run( ansible_runner.run(
timeout=self.timeout if self.timeout > 0 else None,
extravars=self.extra_vars, extravars=self.extra_vars,
host_pattern=self.pattern, host_pattern=self.pattern,
private_data_dir=self.project_dir, private_data_dir=self.project_dir,

View File

@ -7,7 +7,7 @@ from ops.models import Job, JobExecution
from ops.serializers.job import JobSerializer, JobExecutionSerializer from ops.serializers.job import JobSerializer, JobExecutionSerializer
__all__ = ['JobViewSet', 'JobExecutionViewSet', 'JobRunVariableHelpAPIView', __all__ = ['JobViewSet', 'JobExecutionViewSet', 'JobRunVariableHelpAPIView',
'JobAssetDetail', 'JobExecutionTaskDetail','FrequentUsernames'] 'JobAssetDetail', 'JobExecutionTaskDetail', 'FrequentUsernames']
from ops.tasks import run_ops_job_execution from ops.tasks import run_ops_job_execution
from ops.variables import JMS_JOB_VARIABLE_HELP from ops.variables import JMS_JOB_VARIABLE_HELP
@ -110,6 +110,7 @@ class JobExecutionTaskDetail(APIView):
with tmp_to_org(org): with tmp_to_org(org):
execution = get_object_or_404(JobExecution, task_id=task_id) execution = get_object_or_404(JobExecution, task_id=task_id)
return Response(data={ return Response(data={
'status': execution.status,
'is_finished': execution.is_finished, 'is_finished': execution.is_finished,
'is_success': execution.is_success, 'is_success': execution.is_success,
'time_cost': execution.time_cost, 'time_cost': execution.time_cost,

View File

@ -43,9 +43,11 @@ class RunasPolicies(models.TextChoices):
class Modules(models.TextChoices): class Modules(models.TextChoices):
shell = 'shell', _('Shell') shell = 'shell', _('Shell')
winshell = 'win_shell', _('Powershell') winshell = 'win_shell', _('Powershell')
python = 'python', _('Python')
class JobStatus(models.TextChoices): class JobStatus(models.TextChoices):
running = 'running', _('Running') running = 'running', _('Running')
success = 'success', _('Success') success = 'success', _('Success')
timeout = 'timeout', _('Timeout')
failed = 'failed', _('Failed') failed = 'failed', _('Failed')

View File

@ -0,0 +1,33 @@
# Generated by Django 3.2.14 on 2022-12-27 07:20
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('ops', '0034_alter_celerytask_options'),
]
operations = [
migrations.AlterField(
model_name='historicaljob',
name='module',
field=models.CharField(choices=[('shell', 'Shell'), ('win_shell', 'Powershell'), ('python', 'Python')], default='shell', max_length=128, null=True, verbose_name='Module'),
),
migrations.AlterField(
model_name='historicaljob',
name='timeout',
field=models.IntegerField(default=-1, verbose_name='Timeout (Seconds)'),
),
migrations.AlterField(
model_name='job',
name='module',
field=models.CharField(choices=[('shell', 'Shell'), ('win_shell', 'Powershell'), ('python', 'Python')], default='shell', max_length=128, null=True, verbose_name='Module'),
),
migrations.AlterField(
model_name='job',
name='timeout',
field=models.IntegerField(default=-1, verbose_name='Timeout (Seconds)'),
),
]

View File

@ -27,7 +27,7 @@ class Job(JMSOrgBaseModel, PeriodTaskModelMixin):
module = models.CharField(max_length=128, choices=Modules.choices, default=Modules.shell, module = models.CharField(max_length=128, choices=Modules.choices, default=Modules.shell,
verbose_name=_('Module'), null=True) verbose_name=_('Module'), null=True)
chdir = models.CharField(default="", max_length=1024, verbose_name=_('Chdir'), null=True, blank=True) chdir = models.CharField(default="", max_length=1024, verbose_name=_('Chdir'), null=True, blank=True)
timeout = models.IntegerField(default=60, verbose_name=_('Timeout (Seconds)')) timeout = models.IntegerField(default=-1, 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"))
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)
@ -165,12 +165,11 @@ class JobExecution(JMSOrgBaseModel):
if self.current_job.type != 'adhoc': if self.current_job.type != 'adhoc':
return return
result = self.current_job.args result = self.current_job.args
result += " chdir={}".format(self.current_job.chdir) if self.current_job.chdir:
result += " chdir={}".format(self.current_job.chdir)
if self.current_job.module in ['python']: if self.current_job.module in ['python']:
result += " executable={}".format(self.current_job.module) result += " executable={}".format(self.current_job.module)
print(result) return result
return self.job.args
def get_runner(self): def get_runner(self):
inv = self.current_job.inventory inv = self.current_job.inventory
@ -198,6 +197,7 @@ class JobExecution(JMSOrgBaseModel):
runner = AdHocRunner( runner = AdHocRunner(
self.inventory_path, self.inventory_path,
module, module,
timeout=self.current_job.timeout,
module_args=args, module_args=args,
pattern="all", pattern="all",
project_dir=self.private_dir, project_dir=self.private_dir,
@ -238,7 +238,7 @@ class JobExecution(JMSOrgBaseModel):
@property @property
def is_finished(self): def is_finished(self):
return self.status in [JobStatus.success, JobStatus.failed] return self.status in [JobStatus.success, JobStatus.failed, JobStatus.timeout]
@property @property
def is_success(self): def is_success(self):

View File

@ -15,7 +15,7 @@ from .serializers import (
from users.models import User, UserGroup from users.models import User, UserGroup
from assets.models import ( from assets.models import (
Asset, Domain, Label, Node, Asset, Domain, Label, Node,
CommandFilter, CommandFilterRule, GatheredUser CommandFilter, CommandFilterRule
) )
from perms.models import AssetPermission from perms.models import AssetPermission
from orgs.utils import current_org, tmp_to_root_org from orgs.utils import current_org, tmp_to_root_org
@ -28,8 +28,7 @@ logger = get_logger(__file__)
# 部分 org 相关的 model需要清空这些数据之后才能删除该组织 # 部分 org 相关的 model需要清空这些数据之后才能删除该组织
org_related_models = [ org_related_models = [
User, UserGroup, Asset, Label, Domain, Node, Label, User, UserGroup, Asset, Label, Domain, Node, Label,
CommandFilter, CommandFilterRule, GatheredUser, CommandFilter, CommandFilterRule, AssetPermission,
AssetPermission,
] ]

View File

@ -11,6 +11,7 @@ from rest_framework.exceptions import PermissionDenied, NotFound
from assets.utils import KubernetesTree from assets.utils import KubernetesTree
from assets.models import Asset, Account from assets.models import Asset, Account
from assets.const import AliasAccount
from assets.api import SerializeToTreeNodeMixin from assets.api import SerializeToTreeNodeMixin
from authentication.models import ConnectionToken from authentication.models import ConnectionToken
from common.utils import get_object_or_none, lazyproperty from common.utils import get_object_or_none, lazyproperty
@ -157,7 +158,7 @@ class UserGrantedK8sAsTreeApi(SelfOrPKUserMixin, ListAPIView):
raise NotFound('Account is not found') raise NotFound('Account is not found')
account = accounts[0] account = accounts[0]
if account.username in [ if account.username in [
Account.AliasAccount.INPUT, Account.AliasAccount.USER AliasAccount.INPUT, AliasAccount.USER
]: ]:
return token.input_secret return token.input_secret
else: else:

View File

@ -13,6 +13,7 @@ from common.utils import date_expired_default
from common.utils.timezone import local_now from common.utils.timezone import local_now
from perms.const import ActionChoices from perms.const import ActionChoices
from assets.const import AliasAccount
__all__ = ['AssetPermission', 'ActionChoices'] __all__ = ['AssetPermission', 'ActionChoices']
@ -38,7 +39,7 @@ class AssetPermissionQuerySet(models.QuerySet):
def filter_by_accounts(self, accounts): def filter_by_accounts(self, accounts):
q = Q(accounts__contains=list(accounts)) | \ q = Q(accounts__contains=list(accounts)) | \
Q(accounts__contains=Account.AliasAccount.ALL.value) Q(accounts__contains=AliasAccount.ALL.value)
return self.filter(q) return self.filter(q)
@ -127,7 +128,7 @@ class AssetPermission(JMSOrgBaseModel):
""" """
asset_ids = self.get_all_assets(flat=True) asset_ids = self.get_all_assets(flat=True)
q = Q(asset_id__in=asset_ids) q = Q(asset_id__in=asset_ids)
if Account.AliasAccount.ALL not in self.accounts: if AliasAccount.ALL not in self.accounts:
q &= Q(username__in=self.accounts) q &= Q(username__in=self.accounts)
accounts = Account.objects.filter(q).order_by('asset__name', 'name', 'username') accounts = Account.objects.filter(q).order_by('asset__name', 'name', 'username')
if not flat: if not flat:

View File

@ -1,6 +1,7 @@
from collections import defaultdict from collections import defaultdict
from assets.models import Account from assets.models import Account
from assets.const import AliasAccount
from .permission import AssetPermissionUtil from .permission import AssetPermissionUtil
__all__ = ['PermAccountUtil'] __all__ = ['PermAccountUtil']
@ -44,21 +45,21 @@ class PermAccountUtil(AssetPermissionUtil):
cleaned_accounts_expired = defaultdict(list) cleaned_accounts_expired = defaultdict(list)
# @ALL 账号先处理,后面的每个最多映射一个账号 # @ALL 账号先处理,后面的每个最多映射一个账号
all_action_bit = alias_action_bit_mapper.pop(Account.AliasAccount.ALL, None) all_action_bit = alias_action_bit_mapper.pop(AliasAccount.ALL, None)
if all_action_bit: if all_action_bit:
for account in asset_accounts: for account in asset_accounts:
cleaned_accounts_action_bit[account] |= all_action_bit cleaned_accounts_action_bit[account] |= all_action_bit
cleaned_accounts_expired[account].extend( cleaned_accounts_expired[account].extend(
alias_expired_mapper[Account.AliasAccount.ALL] alias_expired_mapper[AliasAccount.ALL]
) )
for alias, action_bit in alias_action_bit_mapper.items(): for alias, action_bit in alias_action_bit_mapper.items():
if alias == Account.AliasAccount.USER: if alias == AliasAccount.USER:
if user.username in username_account_mapper: if user.username in username_account_mapper:
account = username_account_mapper[user.username] account = username_account_mapper[user.username]
else: else:
account = Account.get_user_account(user.username) account = Account.get_user_account(user.username)
elif alias == Account.AliasAccount.INPUT: elif alias == AliasAccount.INPUT:
account = Account.get_manual_account() account = Account.get_manual_account()
elif alias in username_account_mapper: elif alias in username_account_mapper:
account = username_account_mapper[alias] account = username_account_mapper[alias]