From 411d199e18c50340a50f1e3f2bb838843acba45b Mon Sep 17 00:00:00 2001
From: ibuler <ibuler@qq.com>
Date: Tue, 27 Dec 2022 16:53:23 +0800
Subject: [PATCH 1/4] =?UTF-8?q?perf:=20=E4=BF=AE=E6=94=B9=20asset=20serial?=
 =?UTF-8?q?izer?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 apps/assets/api/asset/asset.py          |  1 +
 apps/assets/serializers/asset/common.py | 70 ++++++++++++-------------
 2 files changed, 36 insertions(+), 35 deletions(-)

diff --git a/apps/assets/api/asset/asset.py b/apps/assets/api/asset/asset.py
index cd4166390..1394acbb7 100644
--- a/apps/assets/api/asset/asset.py
+++ b/apps/assets/api/asset/asset.py
@@ -60,6 +60,7 @@ class AssetViewSet(SuggestionMixin, NodeFilterMixin, OrgBulkModelViewSet):
     ordering = ("name",)
     serializer_classes = (
         ("default", serializers.AssetSerializer),
+        ("retrieve", serializers.AssetDetailSerializer),
         ("suggestion", serializers.MiniAssetSerializer),
         ("platform", serializers.PlatformSerializer),
         ("gateways", serializers.GatewaySerializer),
diff --git a/apps/assets/serializers/asset/common.py b/apps/assets/serializers/asset/common.py
index e34af1f5f..36bb66803 100644
--- a/apps/assets/serializers/asset/common.py
+++ b/apps/assets/serializers/asset/common.py
@@ -1,21 +1,22 @@
 # -*- coding: utf-8 -*-
 #
 
-from rest_framework import serializers
-from django.utils.translation import ugettext_lazy as _
-from django.db.transaction import atomic
 from django.db.models import F
+from django.db.transaction import atomic
+from django.utils.translation import ugettext_lazy as _
+from rest_framework import serializers
 
-from common.drf.serializers import WritableNestedModelSerializer
 from common.drf.fields import LabeledChoiceField, ObjectRelatedField
+from common.drf.serializers import WritableNestedModelSerializer
 from orgs.mixins.serializers import BulkOrgResourceSerializerMixin
 from ..account import AccountSerializer
-from ...models import Asset, Node, Platform, Label, Domain, Account, Protocol
 from ...const import Category, AllTypes
+from ...models import Asset, Node, Platform, Label, Domain, Account, Protocol
 
 __all__ = [
     'AssetSerializer', 'AssetSimpleSerializer', 'MiniAssetSerializer',
     'AssetTaskSerializer', 'AssetsTaskSerializer', 'AssetProtocolsSerializer',
+    'AssetDetailSerializer',
 ]
 
 
@@ -66,21 +67,19 @@ 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=_('Accounts'))
-    enabled_info = serializers.SerializerMethodField()
 
     class Meta:
         model = Asset
-        fields_mini = ['id', 'name', 'address', 'enabled_info']
+        fields_mini = ['id', 'name', 'address']
         fields_small = fields_mini + ['is_active', 'comment']
         fields_fk = ['domain', 'platform', 'platform']
         fields_m2m = [
             'nodes', 'labels', 'protocols', 'accounts', 'nodes_display',
         ]
         read_only_fields = [
-            'category', 'type', 'specific', 'info',
-            'connectivity', 'date_verified', 'created_by',
-            'date_created'
+            'category', 'type', 'info',
+            'connectivity', 'date_verified',
+            'created_by', 'date_created'
         ]
         fields = fields_small + fields_fk + fields_m2m + read_only_fields
         extra_kwargs = {
@@ -88,36 +87,13 @@ class AssetSerializer(BulkOrgResourceSerializerMixin, WritableNestedModelSeriali
             'address': {'label': _('Address')},
         }
 
-    def get_field_names(self, declared_fields, info):
-        names = super().get_field_names(declared_fields, info)
-        if self.__class__.__name__ != 'AssetSerializer':
-            names.remove('specific')
-        return names
-
-    @staticmethod
-    def get_enabled_info(obj):
-        platform = obj.platform
-        automation = platform.automation
-        return {
-            'su_enabled': platform.su_enabled,
-            'ping_enabled': automation.ping_enabled,
-            'domain_enabled': platform.domain_enabled,
-            'ansible_enabled': automation.ansible_enabled,
-            'protocols_enabled': platform.protocols_enabled,
-            'gather_facts_enabled': automation.gather_facts_enabled,
-            'push_account_enabled': automation.push_account_enabled,
-            'change_secret_enabled': automation.change_secret_enabled,
-            'verify_account_enabled': automation.verify_account_enabled,
-            'gather_accounts_enabled': automation.gather_accounts_enabled,
-        }
-
     @classmethod
     def setup_eager_loading(cls, queryset):
         """ Perform necessary eager loading of data. """
         queryset = queryset.prefetch_related('domain', 'platform', 'protocols') \
             .annotate(category=F("platform__category")) \
             .annotate(type=F("platform__type"))
-        queryset = queryset.prefetch_related('nodes', 'labels')
+        queryset = queryset.prefetch_related('nodes', 'labels', 'accounts')
         return queryset
 
     def perform_nodes_display_create(self, instance, nodes_display):
@@ -188,6 +164,30 @@ class AssetSerializer(BulkOrgResourceSerializerMixin, WritableNestedModelSeriali
         return instance
 
 
+class AssetDetailSerializer(AssetSerializer):
+    accounts = AssetAccountSerializer(many=True, required=False, label=_('Accounts'))
+    enabled_info = serializers.SerializerMethodField()
+
+    class Meta(AssetSerializer.Meta):
+        fields = AssetSerializer.Meta.fields + ['accounts', 'enabled_info', 'info', 'specific']
+
+    @staticmethod
+    def get_enabled_info(obj):
+        platform = obj.platform
+        automation = platform.automation
+        return {
+            'su_enabled': platform.su_enabled,
+            'ping_enabled': automation.ping_enabled,
+            'domain_enabled': platform.domain_enabled,
+            'ansible_enabled': automation.ansible_enabled,
+            'protocols_enabled': platform.protocols_enabled,
+            'gather_facts_enabled': automation.gather_facts_enabled,
+            'change_secret_enabled': automation.change_secret_enabled,
+            'verify_account_enabled': automation.verify_account_enabled,
+            'gather_accounts_enabled': automation.gather_accounts_enabled,
+        }
+
+
 class MiniAssetSerializer(serializers.ModelSerializer):
     class Meta:
         model = Asset

From 1b9aad594ca764a155c5655e0097d2b13d9136e9 Mon Sep 17 00:00:00 2001
From: ibuler <ibuler@qq.com>
Date: Tue, 27 Dec 2022 16:54:47 +0800
Subject: [PATCH 2/4] perf: remove push account

---
 apps/assets/const/cloud.py          | 1 -
 apps/assets/const/database.py       | 1 -
 apps/assets/const/device.py         | 1 -
 apps/assets/const/host.py           | 1 -
 apps/assets/serializers/platform.py | 2 --
 5 files changed, 6 deletions(-)

diff --git a/apps/assets/const/cloud.py b/apps/assets/const/cloud.py
index be2637ddf..22240cc77 100644
--- a/apps/assets/const/cloud.py
+++ b/apps/assets/const/cloud.py
@@ -25,7 +25,6 @@ class CloudTypes(BaseType):
                 'gather_facts_enabled': False,
                 'verify_account_enabled': False,
                 'change_secret_enabled': False,
-                'push_account_enabled': False,
                 'gather_accounts_enabled': False,
             }
         }
diff --git a/apps/assets/const/database.py b/apps/assets/const/database.py
index 7df58e0a5..53c06bff0 100644
--- a/apps/assets/const/database.py
+++ b/apps/assets/const/database.py
@@ -33,7 +33,6 @@ class DatabaseTypes(BaseType):
                 'gather_accounts_enabled': True,
                 'verify_account_enabled': True,
                 'change_secret_enabled': True,
-                'push_account_enabled': True,
             }
         }
         return constrains
diff --git a/apps/assets/const/device.py b/apps/assets/const/device.py
index 2508900dc..cbd9d7b27 100644
--- a/apps/assets/const/device.py
+++ b/apps/assets/const/device.py
@@ -40,7 +40,6 @@ class DeviceTypes(BaseType):
                 'gather_accounts_enabled': False,
                 'verify_account_enabled': False,
                 'change_secret_enabled': False,
-                'push_account_enabled': False,
             }
         }
 
diff --git a/apps/assets/const/host.py b/apps/assets/const/host.py
index 99e7c4598..91c6b51be 100644
--- a/apps/assets/const/host.py
+++ b/apps/assets/const/host.py
@@ -54,7 +54,6 @@ class HostTypes(BaseType):
                 'gather_accounts_enabled': True,
                 'verify_account_enabled': True,
                 'change_secret_enabled': True,
-                'push_account_enabled': True,
             },
             cls.WINDOWS: {
                 'ansible_config': {
diff --git a/apps/assets/serializers/platform.py b/apps/assets/serializers/platform.py
index 6627592f9..ab72cecc9 100644
--- a/apps/assets/serializers/platform.py
+++ b/apps/assets/serializers/platform.py
@@ -56,8 +56,6 @@ class PlatformAutomationSerializer(serializers.ModelSerializer):
             "gather_facts_method": {"label": "收集信息方式"},
             "verify_account_enabled": {"label": "启用校验账号"},
             "verify_account_method": {"label": "校验账号方式"},
-            "push_account_enabled": {"label": "启用推送账号"},
-            "push_account_method": {"label": "推送账号方式"},
             "change_secret_enabled": {"label": "启用账号改密"},
             "change_secret_method": {"label": "账号改密方式"},
             "gather_accounts_enabled": {"label": "启用账号收集"},

From ddca4dce41839bf298b33bdce826e4d5d2963895 Mon Sep 17 00:00:00 2001
From: Aaron3S <chenyang@fit2cloud.com>
Date: Tue, 27 Dec 2022 15:04:43 +0800
Subject: [PATCH 3/4] =?UTF-8?q?feat:=20=E6=94=AF=E6=8C=81pyhton=E8=84=9A?=
 =?UTF-8?q?=E6=9C=AC?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 apps/ops/models/job.py | 9 ++++-----
 1 file changed, 4 insertions(+), 5 deletions(-)

diff --git a/apps/ops/models/job.py b/apps/ops/models/job.py
index 1b6a33b59..98ec9797f 100644
--- a/apps/ops/models/job.py
+++ b/apps/ops/models/job.py
@@ -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
@@ -238,7 +237,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):

From 1cfe8d9cc88108c9d5bf2b93c5d8ffa66767f2c1 Mon Sep 17 00:00:00 2001
From: Aaron3S <chenyang@fit2cloud.com>
Date: Tue, 27 Dec 2022 16:06:21 +0800
Subject: [PATCH 4/4] =?UTF-8?q?feat:=20=E6=94=AF=E6=8C=81=E8=B6=85?=
 =?UTF-8?q?=E6=97=B6=E6=97=B6=E9=97=B4=E8=AE=BE=E7=BD=AE?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 apps/ops/ansible/callback.py                  |  1 +
 apps/ops/ansible/runner.py                    |  4 ++-
 apps/ops/api/job.py                           |  3 +-
 apps/ops/const.py                             |  2 ++
 .../ops/migrations/0035_auto_20221227_1520.py | 33 +++++++++++++++++++
 apps/ops/models/job.py                        |  3 +-
 6 files changed, 43 insertions(+), 3 deletions(-)
 create mode 100644 apps/ops/migrations/0035_auto_20221227_1520.py

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 98ec9797f..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)
@@ -197,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,