From 0589f7fe33e07775a026832a97847e658e3d4519 Mon Sep 17 00:00:00 2001
From: Eric <xplzv@126.com>
Date: Wed, 20 Mar 2024 19:23:44 +0800
Subject: [PATCH] =?UTF-8?q?perf:=20=E6=94=AF=E6=8C=81=E5=8F=91=E5=B8=83?=
 =?UTF-8?q?=E6=9C=BA=E5=8D=B8=E8=BD=BD=E8=BF=9C=E7=A8=8B=E5=BA=94=E7=94=A8?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 apps/terminal/api/applet/host.py              | 32 +++++++++++++++++--
 .../deploy_applet_host/__init__.py            | 20 ++++++++++++
 .../deploy_applet_host/uninstall_applet.yml   | 11 +++++++
 apps/terminal/models/applet/host.py           | 10 ++++++
 apps/terminal/serializers/applet_host.py      |  9 ++++--
 apps/terminal/tasks.py                        | 11 +++++++
 6 files changed, 89 insertions(+), 4 deletions(-)
 create mode 100644 apps/terminal/automations/deploy_applet_host/uninstall_applet.yml

diff --git a/apps/terminal/api/applet/host.py b/apps/terminal/api/applet/host.py
index 69ed5e0da..6022a3b49 100644
--- a/apps/terminal/api/applet/host.py
+++ b/apps/terminal/api/applet/host.py
@@ -11,9 +11,14 @@ from common.permissions import IsServiceAccount
 from orgs.utils import tmp_to_builtin_org
 from terminal.models import AppletHost, AppletHostDeployment
 from terminal.serializers import (
-    AppletHostSerializer, AppletHostDeploymentSerializer, AppletHostStartupSerializer
+    AppletHostSerializer, AppletHostDeploymentSerializer,
+    AppletHostStartupSerializer, AppletSetupSerializer
+)
+from terminal.tasks import (
+    run_applet_host_deployment,
+    run_applet_host_deployment_install_applet,
+    run_applet_host_deployment_uninstall_applet
 )
-from terminal.tasks import run_applet_host_deployment, run_applet_host_deployment_install_applet
 
 __all__ = ['AppletHostViewSet', 'AppletHostDeploymentViewSet']
 
@@ -56,6 +61,7 @@ class AppletHostDeploymentViewSet(viewsets.ModelViewSet):
     filterset_fields = ['host', ]
     rbac_perms = (
         ('applets', 'terminal.view_AppletHostDeployment'),
+        ('uninstall', 'terminal.change_applethost'),
     )
 
     @staticmethod
@@ -90,7 +96,29 @@ class AppletHostDeploymentViewSet(viewsets.ModelViewSet):
         transaction.on_commit(lambda: self.start_install_applet(applet_host_deployment_ids, applet_id, task_id))
         return Response({'task': task_id}, status=201)
 
+    @action(methods=['post'], detail=False)
+    def uninstall(self, request, *args, **kwargs):
+        serializer = AppletSetupSerializer(data=request.data)
+        serializer.is_valid(raise_exception=True)
+        validated_data = serializer.validated_data
+        hosts = validated_data.pop('hosts', [])
+        applet_id = validated_data.pop('applet_id', '')
+        hosts_qs = AppletHost.objects.filter(id__in=hosts)
+        if not hosts_qs.exists():
+            return Response(status=status.HTTP_404_NOT_FOUND)
+        task_id = str(uuid.uuid4())
+        objs = [AppletHostDeployment(host=host, task=task_id) for host in hosts_qs]
+        applet_host_deployments = AppletHostDeployment.objects.bulk_create(objs)
+        applet_host_deployment_ids = [str(obj.id) for obj in applet_host_deployments]
+        transaction.on_commit(lambda: self.start_uninstall_applet(applet_host_deployment_ids, applet_id, task_id))
+        return Response({'task': task_id}, status=201)
+
     @staticmethod
     def start_install_applet(applet_host_deployment_ids, applet_id, task_id):
         run_applet_host_deployment_install_applet.apply_async((applet_host_deployment_ids, applet_id),
                                                               task_id=str(task_id))
+
+    @staticmethod
+    def start_uninstall_applet(applet_host_deployment_ids, applet_id, task_id):
+        run_applet_host_deployment_uninstall_applet.apply_async((applet_host_deployment_ids, applet_id),
+                                                                task_id=str(task_id))
diff --git a/apps/terminal/automations/deploy_applet_host/__init__.py b/apps/terminal/automations/deploy_applet_host/__init__.py
index d1d76870e..2a96f6823 100644
--- a/apps/terminal/automations/deploy_applet_host/__init__.py
+++ b/apps/terminal/automations/deploy_applet_host/__init__.py
@@ -36,6 +36,9 @@ class DeployAppletHostManager:
     def install_applet(self, **kwargs):
         self._run(self._run_install_applet, **kwargs)
 
+    def uninstall_applet(self, **kwargs):
+        self._run(self._run_uninstall_applet, **kwargs)
+
     def _run_initial_deploy(self, **kwargs):
         playbook = self.generate_initial_playbook
         return self._run_playbook(playbook, **kwargs)
@@ -47,6 +50,13 @@ class DeployAppletHostManager:
             generate_playbook = self.generate_install_all_playbook
         return self._run_playbook(generate_playbook, **kwargs)
 
+    def _run_uninstall_applet(self, **kwargs):
+        if self.applet:
+            generate_playbook = self.generate_uninstall_applet_playbook
+        else:
+            raise ValueError("applet is required for uninstall_applet")
+        return self._run_playbook(generate_playbook, **kwargs)
+
     def generate_initial_playbook(self):
         site_url = settings.SITE_URL
         download_host = settings.APPLET_DOWNLOAD_HOST
@@ -92,6 +102,16 @@ class DeployAppletHostManager:
 
         return self._generate_playbook("install_applet.yml", handler)
 
+    def generate_uninstall_applet_playbook(self):
+        applet_name = self.applet.name
+
+        def handler(plays):
+            for play in plays:
+                play["vars"]["applet_name"] = applet_name
+            return plays
+
+        return self._generate_playbook("uninstall_applet.yml", handler)
+
     def generate_inventory(self):
         inventory = JMSInventory(
             [self.deployment.host], account_policy="privileged_only"
diff --git a/apps/terminal/automations/deploy_applet_host/uninstall_applet.yml b/apps/terminal/automations/deploy_applet_host/uninstall_applet.yml
new file mode 100644
index 000000000..3da0b6d0e
--- /dev/null
+++ b/apps/terminal/automations/deploy_applet_host/uninstall_applet.yml
@@ -0,0 +1,11 @@
+---
+
+- hosts: all
+  vars:
+    applet_name: chrome
+
+  tasks:
+    - name: uninstall applet
+      ansible.windows.win_powershell:
+        script: |
+          tinkerd uninstall --name {{ applet_name }}
diff --git a/apps/terminal/models/applet/host.py b/apps/terminal/models/applet/host.py
index 0a1eab0f5..96eeee56a 100644
--- a/apps/terminal/models/applet/host.py
+++ b/apps/terminal/models/applet/host.py
@@ -168,6 +168,16 @@ class AppletHostDeployment(JMSBaseModel):
         manager = DeployAppletHostManager(self, applet=applet)
         manager.install_applet(**kwargs)
 
+    def uninstall_applet(self, applet_id, **kwargs):
+        from ...automations.deploy_applet_host import DeployAppletHostManager
+        from .applet import Applet
+        if applet_id:
+            applet = Applet.objects.get(id=applet_id)
+        else:
+            applet = None
+        manager = DeployAppletHostManager(self, applet=applet)
+        manager.uninstall_applet(**kwargs)
+
     def save_task(self, task):
         self.task = task
         self.save(update_fields=['task'])
diff --git a/apps/terminal/serializers/applet_host.py b/apps/terminal/serializers/applet_host.py
index 1e2b0bd16..00cabf2e7 100644
--- a/apps/terminal/serializers/applet_host.py
+++ b/apps/terminal/serializers/applet_host.py
@@ -15,7 +15,7 @@ from ..models import AppletHost, AppletHostDeployment
 __all__ = [
     'AppletHostSerializer', 'AppletHostDeploymentSerializer',
     'AppletHostAccountSerializer', 'AppletHostAppletReportSerializer',
-    'AppletHostStartupSerializer'
+    'AppletHostStartupSerializer', 'AppletSetupSerializer'
 ]
 
 
@@ -143,7 +143,7 @@ class AppletHostDeploymentSerializer(serializers.ModelSerializer):
             'status', 'date_created', 'date_updated',
             'date_start', 'date_finished'
         ]
-        write_only_fields = ['install_applets',]
+        write_only_fields = ['install_applets', ]
         fields = fields_mini + ['comment'] + read_only_fields + write_only_fields
 
 
@@ -161,3 +161,8 @@ class AppletHostAppletReportSerializer(serializers.Serializer):
 
 class AppletHostStartupSerializer(serializers.Serializer):
     pass
+
+
+class AppletSetupSerializer(serializers.Serializer):
+    hosts = serializers.ListField(child=serializers.UUIDField(label=_('Host ID')), label=_('Hosts'))
+    applet_id = serializers.UUIDField(label=_('Applet ID'))
diff --git a/apps/terminal/tasks.py b/apps/terminal/tasks.py
index 71bb2aba7..b8bd89f73 100644
--- a/apps/terminal/tasks.py
+++ b/apps/terminal/tasks.py
@@ -108,6 +108,17 @@ def run_applet_host_deployment_install_applet(ids, applet_id):
             deployment.install_applet(applet_id)
 
 
+@shared_task(
+    verbose_name=_('Uninstall applet'),
+    activity_callback=lambda self, ids, applet_id, *args, **kwargs: (ids,)
+)
+def run_applet_host_deployment_uninstall_applet(ids, applet_id):
+    with tmp_to_builtin_org(system=1):
+        for did in ids:
+            deployment = AppletHostDeployment.objects.get(id=did)
+            deployment.uninstall_applet(applet_id)
+
+
 @shared_task(
     verbose_name=_('Generate applet host accounts'),
     activity_callback=lambda self, host_id, *args, **kwargs: ([host_id],)