diff --git a/apps/assets/api/__init__.py b/apps/assets/api/__init__.py index 8c71ed367..67a935f84 100644 --- a/apps/assets/api/__init__.py +++ b/apps/assets/api/__init__.py @@ -4,7 +4,6 @@ from .automations import * from .category import * from .domain import * from .favorite_asset import * -from .gathered_user import * from .label import * from .mixin import * from .node import * diff --git a/apps/assets/api/gathered_user.py b/apps/assets/api/gathered_user.py deleted file mode 100644 index 8fcf59456..000000000 --- a/apps/assets/api/gathered_user.py +++ /dev/null @@ -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'] diff --git a/apps/assets/automations/gather_accounts/manager.py b/apps/assets/automations/gather_accounts/manager.py index 40253be82..da1b44abe 100644 --- a/apps/assets/automations/gather_accounts/manager.py +++ b/apps/assets/automations/gather_accounts/manager.py @@ -1,8 +1,9 @@ +from django.utils.translation import ugettext_lazy as _ + 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 .filter import GatherAccountsFilter -from ...models import GatheredUser from ..base.manager import BasePlaybookManager logger = get_logger(__name__) @@ -26,20 +27,33 @@ class GatherAccountsManager(BasePlaybookManager): result = GatherAccountsFilter(host).run(self.method_id_meta_mapper, 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): info = result.get('debug', {}).get('res', {}).get('info', {}) asset = self.host_asset_mapper.get(host) - org_id = asset.org_id if asset and info: result = self.filter_success_result(host, info) - with tmp_to_org(org_id): - 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) + self.bulk_create_accounts(asset, result) else: logger.error("Not found info".format(host)) diff --git a/apps/assets/const/account.py b/apps/assets/const/account.py index ebeb855ed..4c15bb519 100644 --- a/apps/assets/const/account.py +++ b/apps/assets/const/account.py @@ -13,3 +13,14 @@ class SecretType(TextChoices): SSH_KEY = 'ssh_key', _('SSH key') ACCESS_KEY = 'access_key', _('Access key') 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') diff --git a/apps/assets/filters.py b/apps/assets/filters.py index f1b869805..c60d492e3 100644 --- a/apps/assets/filters.py +++ b/apps/assets/filters.py @@ -126,17 +126,6 @@ class LabelFilterBackend(filters.BaseFilterBackend): 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): def filter_queryset(self, request, queryset, view): ips = request.query_params.get('ips') diff --git a/apps/assets/migrations/0119_auto_20221227_1740.py b/apps/assets/migrations/0119_auto_20221227_1740.py new file mode 100644 index 000000000..63048163d --- /dev/null +++ b/apps/assets/migrations/0119_auto_20221227_1740.py @@ -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', + ), + ] diff --git a/apps/assets/models/__init__.py b/apps/assets/models/__init__.py index 6d9716c39..78e6e579d 100644 --- a/apps/assets/models/__init__.py +++ b/apps/assets/models/__init__.py @@ -7,7 +7,6 @@ from .gateway import * from .domain import * from .node import * from .utils import * -from .gathered_user import * from .favorite_asset import * from .account import * from .backup import * diff --git a/apps/assets/models/account.py b/apps/assets/models/account.py index 68a6fa47e..e0e87d37c 100644 --- a/apps/assets/models/account.py +++ b/apps/assets/models/account.py @@ -3,6 +3,7 @@ from django.utils.translation import gettext_lazy as _ from simple_history.models import HistoricalRecords from common.utils import lazyproperty +from ..const import AliasAccount, Source from .base import AbsConnectivity, BaseAccount __all__ = ['Account', 'AccountTemplate'] @@ -40,11 +41,6 @@ class AccountHistoricalRecords(HistoricalRecords): class Account(AbsConnectivity, BaseAccount): - class AliasAccount(models.TextChoices): - ALL = '@ALL', _('All') - INPUT = '@INPUT', _('Manual input') - USER = '@USER', _('Dynamic user') - asset = models.ForeignKey( 'assets.Asset', related_name='accounts', on_delete=models.CASCADE, verbose_name=_('Asset') @@ -55,6 +51,7 @@ class Account(AbsConnectivity, BaseAccount): ) version = models.IntegerField(default=0, verbose_name=_('Version')) history = AccountHistoricalRecords(included_fields=['id', 'secret', 'secret_type', 'version']) + source = models.CharField(max_length=30, default=Source.LOCAL, verbose_name=_('Source')) class Meta: verbose_name = _('Account') @@ -89,12 +86,12 @@ class Account(AbsConnectivity, BaseAccount): @classmethod def get_manual_account(cls): """ @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 def get_user_account(cls, username): """ @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): """ 排除自己和以自己为 su-from 的账号 """ diff --git a/apps/assets/models/gathered_user.py b/apps/assets/models/gathered_user.py deleted file mode 100644 index 4f7ae4bb8..000000000 --- a/apps/assets/models/gathered_user.py +++ /dev/null @@ -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) diff --git a/apps/assets/serializers/__init__.py b/apps/assets/serializers/__init__.py index 7d82131a7..f70a94bbf 100644 --- a/apps/assets/serializers/__init__.py +++ b/apps/assets/serializers/__init__.py @@ -6,7 +6,6 @@ from .label import * from .node import * from .gateway import * from .domain import * -from .gathered_user import * from .favorite_asset import * from .account import * from .platform import * diff --git a/apps/assets/serializers/account/account.py b/apps/assets/serializers/account/account.py index 218155b66..c2eb786dd 100644 --- a/apps/assets/serializers/account/account.py +++ b/apps/assets/serializers/account/account.py @@ -1,7 +1,7 @@ from django.utils.translation import ugettext_lazy as _ 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.tasks import push_accounts_to_assets from common.drf.fields import ObjectRelatedField, LabeledChoiceField @@ -74,6 +74,7 @@ class AccountAssetSerializer(serializers.ModelSerializer): class AccountSerializer(AccountSerializerCreateMixin, BaseAccountSerializer): asset = AccountAssetSerializer(label=_('Asset')) + source = LabeledChoiceField(choices=Source.choices, label=_("Source"), read_only=True) su_from = ObjectRelatedField( required=False, queryset=Account.objects, allow_null=True, allow_empty=True, label=_('Su from'), attrs=('id', 'name', 'username') @@ -83,7 +84,7 @@ class AccountSerializer(AccountSerializerCreateMixin, BaseAccountSerializer): model = Account fields = BaseAccountSerializer.Meta.fields \ + ['su_from', 'version', 'asset'] \ - + ['template', 'push_now'] + + ['template', 'push_now', 'source'] extra_kwargs = { **BaseAccountSerializer.Meta.extra_kwargs, 'name': {'required': False, 'allow_null': True}, diff --git a/apps/assets/serializers/asset/common.py b/apps/assets/serializers/asset/common.py index 36bb66803..e2a84fe5a 100644 --- a/apps/assets/serializers/asset/common.py +++ b/apps/assets/serializers/asset/common.py @@ -67,6 +67,7 @@ class AssetSerializer(BulkOrgResourceSerializerMixin, WritableNestedModelSeriali nodes = ObjectRelatedField(many=True, required=False, queryset=Node.objects, label=_('Nodes')) labels = AssetLabelSerializer(many=True, required=False, label=_('Labels')) protocols = AssetProtocolsSerializer(many=True, required=False, label=_('Protocols')) + accounts = AssetAccountSerializer(many=True, required=False, label=_('Account')) class Meta: model = Asset @@ -85,6 +86,7 @@ class AssetSerializer(BulkOrgResourceSerializerMixin, WritableNestedModelSeriali extra_kwargs = { 'name': {'label': _("Name")}, 'address': {'label': _('Address')}, + 'nodes_display': {'label': _('Node path')}, } @classmethod diff --git a/apps/assets/serializers/gathered_user.py b/apps/assets/serializers/gathered_user.py deleted file mode 100644 index a0b58de45..000000000 --- a/apps/assets/serializers/gathered_user.py +++ /dev/null @@ -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'}, - } diff --git a/apps/assets/urls/api_urls.py b/apps/assets/urls/api_urls.py index 0279aa785..dd42c44eb 100644 --- a/apps/assets/urls/api_urls.py +++ b/apps/assets/urls/api_urls.py @@ -23,7 +23,6 @@ router.register(r'labels', api.LabelViewSet, 'label') router.register(r'nodes', api.NodeViewSet, 'node') router.register(r'domains', api.DomainViewSet, 'domain') 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'account-backup-plans', api.AccountBackupPlanViewSet, 'account-backup') router.register(r'account-backup-plan-executions', api.AccountBackupPlanExecutionViewSet, 'account-backup-execution') @@ -51,7 +50,6 @@ urlpatterns = [ name='account-secret-history'), 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/', api.NodeChildrenApi.as_view(), name='node-children'), path('nodes/children/', api.NodeChildrenApi.as_view(), name='node-children-2'), diff --git a/apps/audits/const.py b/apps/audits/const.py index 62edc62a2..6987de94f 100644 --- a/apps/audits/const.py +++ b/apps/audits/const.py @@ -17,12 +17,10 @@ MODELS_NEED_RECORD = ( "LoginAssetACL", "LoginConfirmSetting", # assets - 'Asset', 'Node', 'AdminUser', 'SystemUser', 'Domain', 'Gateway', 'CommandFilterRule', + 'Asset', 'Node', 'Domain', 'Gateway', 'CommandFilterRule', 'CommandFilter', 'Platform', 'Label', - # applications - 'Application', # account - 'AuthBook', + 'Account', # orgs "Organization", # settings @@ -36,8 +34,7 @@ MODELS_NEED_RECORD = ( # rbac 'Role', 'SystemRole', 'OrgRole', 'RoleBinding', 'OrgRoleBinding', 'SystemRoleBinding', # xpack - 'License', 'Account', 'SyncInstanceTask', 'ChangeAuthPlan', - 'GatherUserTask', 'Interface', + 'License', 'Account', 'SyncInstanceTask', 'Interface', ) diff --git a/apps/locale/ja/LC_MESSAGES/django.mo b/apps/locale/ja/LC_MESSAGES/django.mo index f98c635ae..454a324f7 100644 --- a/apps/locale/ja/LC_MESSAGES/django.mo +++ b/apps/locale/ja/LC_MESSAGES/django.mo @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a533ee3b36cdd61fe239be159cc80362e5cd358f134857987029bde8e4c1231e -size 119530 +oid sha256:93524bfafbdfcb59e20920619c934b1df7b040573e98d1f1f481c73126110630 +size 119578 diff --git a/apps/locale/ja/LC_MESSAGES/django.po b/apps/locale/ja/LC_MESSAGES/django.po index 33caf18a8..652632d49 100644 --- a/apps/locale/ja/LC_MESSAGES/django.po +++ b/apps/locale/ja/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\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" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -1308,12 +1308,16 @@ msgstr "プロトコル" msgid "Address" msgstr "アドレス" -#: assets/serializers/asset/common.py:156 +#: assets/serializers/asset/common.py:89 +msgid "Node path" +msgstr "ノードパスです" + +#: assets/serializers/asset/common.py:157 #, fuzzy msgid "Platform not exist" msgstr "アプリが存在しません" -#: assets/serializers/asset/common.py:172 +#: assets/serializers/asset/common.py:173 #, fuzzy msgid "Protocol is required: {}" msgstr "プロトコル重複: {}" @@ -7443,9 +7447,6 @@ msgstr "コミュニティ版" #~ msgid "Apps amount" #~ msgstr "アプリの量" -#~ msgid "Nodes amount" -#~ msgstr "ノード量" - #~ msgid "Login mode display" #~ msgstr "ログインモード表示" diff --git a/apps/locale/zh/LC_MESSAGES/django.mo b/apps/locale/zh/LC_MESSAGES/django.mo index acd56e90a..d938f8a06 100644 --- a/apps/locale/zh/LC_MESSAGES/django.mo +++ b/apps/locale/zh/LC_MESSAGES/django.mo @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7496f27202b5dcd9d677a70f9efc86c30b74fc306d6b5fa7202238c15c845971 -size 105814 +oid sha256:8ea4225734c8f9df88bb72379ccd48b50d833766fbde3ce64cacf5eb6f87082b +size 105853 diff --git a/apps/locale/zh/LC_MESSAGES/django.po b/apps/locale/zh/LC_MESSAGES/django.po index 2fe65075a..a08e9854c 100644 --- a/apps/locale/zh/LC_MESSAGES/django.po +++ b/apps/locale/zh/LC_MESSAGES/django.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: JumpServer 0.3.3\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" "Last-Translator: ibuler \n" "Language-Team: JumpServer team\n" @@ -1246,11 +1246,15 @@ msgstr "协议组" msgid "Address" 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" msgstr "平台不存在" -#: assets/serializers/asset/common.py:172 +#: assets/serializers/asset/common.py:173 msgid "Protocol is required: {}" msgstr "协议是必填的: {}" @@ -7217,9 +7221,6 @@ msgstr "社区版" #~ msgid "Apps amount" #~ msgstr "应用数量" -#~ msgid "Nodes amount" -#~ msgstr "节点数量" - #~ msgid "Login mode display" #~ msgstr "认证方式名称" diff --git a/apps/ops/ansible/callback.py b/apps/ops/ansible/callback.py index 7d2b1f39d..4bcb9be60 100644 --- a/apps/ops/ansible/callback.py +++ b/apps/ops/ansible/callback.py @@ -8,6 +8,7 @@ class DefaultCallback: 'failed': 'failed', 'running': 'running', 'pending': 'pending', + 'timeout': 'timeout', 'unknown': 'unknown' } diff --git a/apps/ops/ansible/runner.py b/apps/ops/ansible/runner.py index 8c7517ade..280f8f05c 100644 --- a/apps/ops/ansible/runner.py +++ b/apps/ops/ansible/runner.py @@ -14,7 +14,7 @@ class AdHocRunner: ] 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.inventory = inventory self.pattern = pattern @@ -25,6 +25,7 @@ class AdHocRunner: self.runner = None self.extra_vars = extra_vars self.dry_run = dry_run + self.timeout = timeout def check_module(self): if self.module not in self.cmd_modules_choices: @@ -41,6 +42,7 @@ class AdHocRunner: os.mkdir(self.project_dir, 0o755) ansible_runner.run( + timeout=self.timeout if self.timeout > 0 else None, extravars=self.extra_vars, host_pattern=self.pattern, private_data_dir=self.project_dir, diff --git a/apps/ops/api/job.py b/apps/ops/api/job.py index 1c7cac33d..817e5040e 100644 --- a/apps/ops/api/job.py +++ b/apps/ops/api/job.py @@ -7,7 +7,7 @@ from ops.models import Job, JobExecution from ops.serializers.job import JobSerializer, JobExecutionSerializer __all__ = ['JobViewSet', 'JobExecutionViewSet', 'JobRunVariableHelpAPIView', - 'JobAssetDetail', 'JobExecutionTaskDetail','FrequentUsernames'] + 'JobAssetDetail', 'JobExecutionTaskDetail', 'FrequentUsernames'] from ops.tasks import run_ops_job_execution from ops.variables import JMS_JOB_VARIABLE_HELP @@ -110,6 +110,7 @@ class JobExecutionTaskDetail(APIView): with tmp_to_org(org): execution = get_object_or_404(JobExecution, task_id=task_id) return Response(data={ + 'status': execution.status, 'is_finished': execution.is_finished, 'is_success': execution.is_success, 'time_cost': execution.time_cost, diff --git a/apps/ops/const.py b/apps/ops/const.py index 8288a663e..c383ef3c7 100644 --- a/apps/ops/const.py +++ b/apps/ops/const.py @@ -43,9 +43,11 @@ class RunasPolicies(models.TextChoices): class Modules(models.TextChoices): shell = 'shell', _('Shell') winshell = 'win_shell', _('Powershell') + python = 'python', _('Python') class JobStatus(models.TextChoices): running = 'running', _('Running') success = 'success', _('Success') + timeout = 'timeout', _('Timeout') failed = 'failed', _('Failed') diff --git a/apps/ops/migrations/0035_auto_20221227_1520.py b/apps/ops/migrations/0035_auto_20221227_1520.py new file mode 100644 index 000000000..36752a9bb --- /dev/null +++ b/apps/ops/migrations/0035_auto_20221227_1520.py @@ -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)'), + ), + ] diff --git a/apps/ops/models/job.py b/apps/ops/models/job.py index 1b6a33b59..85f960df1 100644 --- a/apps/ops/models/job.py +++ b/apps/ops/models/job.py @@ -27,7 +27,7 @@ class Job(JMSOrgBaseModel, PeriodTaskModelMixin): module = models.CharField(max_length=128, choices=Modules.choices, default=Modules.shell, verbose_name=_('Module'), null=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) 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) @@ -165,12 +165,11 @@ class JobExecution(JMSOrgBaseModel): if self.current_job.type != 'adhoc': return 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']: result += " executable={}".format(self.current_job.module) - print(result) - return self.job.args + return result def get_runner(self): inv = self.current_job.inventory @@ -198,6 +197,7 @@ class JobExecution(JMSOrgBaseModel): runner = AdHocRunner( self.inventory_path, module, + timeout=self.current_job.timeout, module_args=args, pattern="all", project_dir=self.private_dir, @@ -238,7 +238,7 @@ class JobExecution(JMSOrgBaseModel): @property def is_finished(self): - return self.status in [JobStatus.success, JobStatus.failed] + return self.status in [JobStatus.success, JobStatus.failed, JobStatus.timeout] @property def is_success(self): diff --git a/apps/orgs/api.py b/apps/orgs/api.py index da9b1530f..e18ddf55e 100644 --- a/apps/orgs/api.py +++ b/apps/orgs/api.py @@ -15,7 +15,7 @@ from .serializers import ( from users.models import User, UserGroup from assets.models import ( Asset, Domain, Label, Node, - CommandFilter, CommandFilterRule, GatheredUser + CommandFilter, CommandFilterRule ) from perms.models import AssetPermission from orgs.utils import current_org, tmp_to_root_org @@ -28,8 +28,7 @@ logger = get_logger(__file__) # 部分 org 相关的 model,需要清空这些数据之后才能删除该组织 org_related_models = [ User, UserGroup, Asset, Label, Domain, Node, Label, - CommandFilter, CommandFilterRule, GatheredUser, - AssetPermission, + CommandFilter, CommandFilterRule, AssetPermission, ] diff --git a/apps/perms/api/user_permission/tree/node_with_asset.py b/apps/perms/api/user_permission/tree/node_with_asset.py index 1dcdcc43d..9aa73963b 100644 --- a/apps/perms/api/user_permission/tree/node_with_asset.py +++ b/apps/perms/api/user_permission/tree/node_with_asset.py @@ -11,6 +11,7 @@ from rest_framework.exceptions import PermissionDenied, NotFound from assets.utils import KubernetesTree from assets.models import Asset, Account +from assets.const import AliasAccount from assets.api import SerializeToTreeNodeMixin from authentication.models import ConnectionToken from common.utils import get_object_or_none, lazyproperty @@ -157,7 +158,7 @@ class UserGrantedK8sAsTreeApi(SelfOrPKUserMixin, ListAPIView): raise NotFound('Account is not found') account = accounts[0] if account.username in [ - Account.AliasAccount.INPUT, Account.AliasAccount.USER + AliasAccount.INPUT, AliasAccount.USER ]: return token.input_secret else: diff --git a/apps/perms/models/asset_permission.py b/apps/perms/models/asset_permission.py index 989f10cbc..2af68832c 100644 --- a/apps/perms/models/asset_permission.py +++ b/apps/perms/models/asset_permission.py @@ -13,6 +13,7 @@ from common.utils import date_expired_default from common.utils.timezone import local_now from perms.const import ActionChoices +from assets.const import AliasAccount __all__ = ['AssetPermission', 'ActionChoices'] @@ -38,7 +39,7 @@ class AssetPermissionQuerySet(models.QuerySet): def filter_by_accounts(self, accounts): q = Q(accounts__contains=list(accounts)) | \ - Q(accounts__contains=Account.AliasAccount.ALL.value) + Q(accounts__contains=AliasAccount.ALL.value) return self.filter(q) @@ -127,7 +128,7 @@ class AssetPermission(JMSOrgBaseModel): """ asset_ids = self.get_all_assets(flat=True) 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) accounts = Account.objects.filter(q).order_by('asset__name', 'name', 'username') if not flat: diff --git a/apps/perms/utils/account.py b/apps/perms/utils/account.py index d4ce2e402..bdec2b934 100644 --- a/apps/perms/utils/account.py +++ b/apps/perms/utils/account.py @@ -1,6 +1,7 @@ from collections import defaultdict from assets.models import Account +from assets.const import AliasAccount from .permission import AssetPermissionUtil __all__ = ['PermAccountUtil'] @@ -44,21 +45,21 @@ class PermAccountUtil(AssetPermissionUtil): cleaned_accounts_expired = defaultdict(list) # @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: for account in asset_accounts: cleaned_accounts_action_bit[account] |= all_action_bit 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(): - if alias == Account.AliasAccount.USER: + if alias == AliasAccount.USER: if user.username in username_account_mapper: account = username_account_mapper[user.username] else: account = Account.get_user_account(user.username) - elif alias == Account.AliasAccount.INPUT: + elif alias == AliasAccount.INPUT: account = Account.get_manual_account() elif alias in username_account_mapper: account = username_account_mapper[alias]