mirror of https://github.com/jumpserver/jumpserver
				
				
				
			pref: 修改 applet host deploy
							parent
							
								
									8f9eb64c8d
								
							
						
					
					
						commit
						12b74093e2
					
				| 
						 | 
				
			
			@ -9,8 +9,9 @@ __all__ = ['JMSInventory']
 | 
			
		|||
 | 
			
		||||
 | 
			
		||||
class JMSInventory:
 | 
			
		||||
    def __init__(self, manager, assets=None, account_policy='smart',
 | 
			
		||||
                 account_prefer='root,administrator', host_callback=None):
 | 
			
		||||
    def __init__(self, assets, account_policy='smart',
 | 
			
		||||
                 account_prefer='root,administrator',
 | 
			
		||||
                 host_callback=None):
 | 
			
		||||
        """
 | 
			
		||||
        :param assets:
 | 
			
		||||
        :param account_prefer: account username name if not set use account_policy
 | 
			
		||||
| 
						 | 
				
			
			@ -79,10 +80,7 @@ class JMSInventory:
 | 
			
		|||
        ssh_protocol_matched = list(filter(lambda x: x.name == 'ssh', protocols))
 | 
			
		||||
        ssh_protocol = ssh_protocol_matched[0] if ssh_protocol_matched else None
 | 
			
		||||
        host['ansible_host'] = asset.address
 | 
			
		||||
        if asset.port == 0:
 | 
			
		||||
        host['ansible_port'] = ssh_protocol.port if ssh_protocol else 22
 | 
			
		||||
        else:
 | 
			
		||||
            host['ansible_port'] = asset.port
 | 
			
		||||
 | 
			
		||||
        su_from = account.su_from
 | 
			
		||||
        if platform.su_enabled and su_from:
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -99,7 +99,7 @@ class CeleryPeriodTaskViewSet(CommonApiMixin, viewsets.ModelViewSet):
 | 
			
		|||
 | 
			
		||||
 | 
			
		||||
class CeleryTaskViewSet(CommonApiMixin, viewsets.ReadOnlyModelViewSet):
 | 
			
		||||
    queryset = CeleryTask.objects.filter(name__in=['ops.tasks.hello', 'ops.tasks.hello_error', 'ops.tasks.hello_random'])
 | 
			
		||||
    queryset = CeleryTask.objects.all()
 | 
			
		||||
    serializer_class = CeleryTaskSerializer
 | 
			
		||||
    http_method_names = ('get', 'head', 'options',)
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,7 +1,7 @@
 | 
			
		|||
import os.path
 | 
			
		||||
import shutil
 | 
			
		||||
import zipfile
 | 
			
		||||
import yaml
 | 
			
		||||
import os.path
 | 
			
		||||
 | 
			
		||||
from django.core.files.storage import default_storage
 | 
			
		||||
from rest_framework import viewsets
 | 
			
		||||
| 
						 | 
				
			
			@ -9,12 +9,16 @@ from rest_framework.decorators import action
 | 
			
		|||
from rest_framework.response import Response
 | 
			
		||||
from rest_framework.serializers import ValidationError
 | 
			
		||||
 | 
			
		||||
from terminal import serializers, models
 | 
			
		||||
from terminal import serializers
 | 
			
		||||
from terminal.models import AppletPublication, Applet
 | 
			
		||||
from terminal.serializers import AppletUploadSerializer
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
__all__ = ['AppletViewSet', 'AppletPublicationViewSet']
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class AppletViewSet(viewsets.ModelViewSet):
 | 
			
		||||
    queryset = models.Applet.objects.all()
 | 
			
		||||
    queryset = Applet.objects.all()
 | 
			
		||||
    serializer_class = serializers.AppletSerializer
 | 
			
		||||
    rbac_perms = {
 | 
			
		||||
        'upload': 'terminal.add_applet',
 | 
			
		||||
| 
						 | 
				
			
			@ -67,7 +71,7 @@ class AppletViewSet(viewsets.ModelViewSet):
 | 
			
		|||
        name = manifest['name']
 | 
			
		||||
        update = request.query_params.get('update')
 | 
			
		||||
 | 
			
		||||
        instance = models.Applet.objects.filter(name=name).first()
 | 
			
		||||
        instance = Applet.objects.filter(name=name).first()
 | 
			
		||||
        if instance and not update:
 | 
			
		||||
            return Response({'error': 'Applet already exists: {}'.format(name)}, status=400)
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -82,5 +86,5 @@ class AppletViewSet(viewsets.ModelViewSet):
 | 
			
		|||
 | 
			
		||||
 | 
			
		||||
class AppletPublicationViewSet(viewsets.ModelViewSet):
 | 
			
		||||
    queryset = models.AppletPublication.objects.all()
 | 
			
		||||
    queryset = AppletPublication.objects.all()
 | 
			
		||||
    serializer_class = serializers.AppletPublicationSerializer
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,23 +1,35 @@
 | 
			
		|||
from rest_framework import viewsets
 | 
			
		||||
from rest_framework.decorators import action
 | 
			
		||||
from rest_framework.response import Response
 | 
			
		||||
 | 
			
		||||
from orgs.utils import tmp_to_builtin_org
 | 
			
		||||
from terminal import serializers, models
 | 
			
		||||
from terminal import serializers
 | 
			
		||||
from terminal.models import AppletHost, Applet
 | 
			
		||||
from terminal.tasks import run_applet_host_deployment
 | 
			
		||||
 | 
			
		||||
__all__ = ['AppletHostViewSet', 'AppletHostDeploymentViewSet']
 | 
			
		||||
__all__ = ['AppletHostViewSet']
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class AppletHostViewSet(viewsets.ModelViewSet):
 | 
			
		||||
    serializer_class = serializers.AppletHostSerializer
 | 
			
		||||
 | 
			
		||||
    def get_queryset(self):
 | 
			
		||||
        return models.AppletHost.objects.all()
 | 
			
		||||
        return AppletHost.objects.all()
 | 
			
		||||
 | 
			
		||||
    def dispatch(self, request, *args, **kwargs):
 | 
			
		||||
        with tmp_to_builtin_org(system=1):
 | 
			
		||||
            return super().dispatch(request, *args, **kwargs)
 | 
			
		||||
 | 
			
		||||
    @action(methods=['post'], detail=True)
 | 
			
		||||
    def deploy(self, request):
 | 
			
		||||
        from terminal.automations.deploy_applet_host.manager import DeployAppletHostManager
 | 
			
		||||
        manager = DeployAppletHostManager(self)
 | 
			
		||||
        manager.run()
 | 
			
		||||
 | 
			
		||||
class AppletHostDeploymentViewSet(viewsets.ModelViewSet):
 | 
			
		||||
    queryset = models.AppletHostDeployment.objects.all()
 | 
			
		||||
    serializer_class = serializers.AppletHostDeploymentSerializer
 | 
			
		||||
    @action(methods=['get'], detail=True, url_path='')
 | 
			
		||||
    def not_published_applets(self, request, *args, **kwargs):
 | 
			
		||||
        instance = self.get_object()
 | 
			
		||||
        applets = Applet.objects.exclude(id__in=instance.applets.all())
 | 
			
		||||
        serializer = serializers.AppletSerializer(applets, many=True)
 | 
			
		||||
        return Response(serializer.data)
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,43 @@
 | 
			
		|||
import os
 | 
			
		||||
import datetime
 | 
			
		||||
import shutil
 | 
			
		||||
from django.conf import settings
 | 
			
		||||
 | 
			
		||||
from ops.ansible import PlaybookRunner, JMSInventory
 | 
			
		||||
 | 
			
		||||
CURRENT_DIR = os.path.dirname(os.path.abspath(__file__))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class DeployAppletHostManager:
 | 
			
		||||
    def __init__(self, applet_host):
 | 
			
		||||
        self.applet_host = applet_host
 | 
			
		||||
        self.run_dir = self.get_run_dir()
 | 
			
		||||
 | 
			
		||||
    @staticmethod
 | 
			
		||||
    def get_run_dir():
 | 
			
		||||
        base = os.path.join(settings.ANSIBLE_DIR, 'applet_host_deploy')
 | 
			
		||||
        now = datetime.datetime.now().strftime('%Y%m%d%H%M%S')
 | 
			
		||||
        return os.path.join(base, now)
 | 
			
		||||
 | 
			
		||||
    def generate_playbook(self):
 | 
			
		||||
        playbook_src = os.path.join(CURRENT_DIR, 'playbook.yml')
 | 
			
		||||
        playbook_dir = os.path.join(self.run_dir, 'playbook')
 | 
			
		||||
        playbook_dst = os.path.join(playbook_dir, 'main.yml')
 | 
			
		||||
        os.makedirs(playbook_dir, exist_ok=True)
 | 
			
		||||
        shutil.copy(playbook_src, playbook_dst)
 | 
			
		||||
        return playbook_dst
 | 
			
		||||
 | 
			
		||||
    def generate_inventory(self):
 | 
			
		||||
        inventory = JMSInventory([self.applet_host], account_policy='privileged_only')
 | 
			
		||||
        inventory_dir = os.path.join(self.run_dir, 'inventory')
 | 
			
		||||
        inventory_path = os.path.join(inventory_dir, 'hosts.yml')
 | 
			
		||||
        inventory.write_to_file(inventory_path)
 | 
			
		||||
        return inventory_path
 | 
			
		||||
 | 
			
		||||
    def run(self, **kwargs):
 | 
			
		||||
        inventory = self.generate_inventory()
 | 
			
		||||
        playbook = self.generate_playbook()
 | 
			
		||||
        runner = PlaybookRunner(
 | 
			
		||||
            inventory=inventory, playbook=playbook, project_dir=self.run_dir
 | 
			
		||||
        )
 | 
			
		||||
        return runner.run(**kwargs)
 | 
			
		||||
| 
						 | 
				
			
			@ -1,6 +1,6 @@
 | 
			
		|||
---
 | 
			
		||||
 | 
			
		||||
- hosts: windows
 | 
			
		||||
- hosts: all
 | 
			
		||||
  vars:
 | 
			
		||||
    - DownloadHost: https://demo.jumpserver.org/download
 | 
			
		||||
    - RDS_Licensing: enabled
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,42 @@
 | 
			
		|||
# Generated by Django 3.2.14 on 2022-10-28 07:44
 | 
			
		||||
 | 
			
		||||
from django.db import migrations, models
 | 
			
		||||
import django.db.models.deletion
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Migration(migrations.Migration):
 | 
			
		||||
 | 
			
		||||
    dependencies = [
 | 
			
		||||
        ('terminal', '0054_auto_20221027_1125'),
 | 
			
		||||
    ]
 | 
			
		||||
 | 
			
		||||
    operations = [
 | 
			
		||||
        migrations.AddField(
 | 
			
		||||
            model_name='applet',
 | 
			
		||||
            name='hosts',
 | 
			
		||||
            field=models.ManyToManyField(through='terminal.AppletPublication', to='terminal.AppletHost', verbose_name='Hosts'),
 | 
			
		||||
        ),
 | 
			
		||||
        migrations.AddField(
 | 
			
		||||
            model_name='applethost',
 | 
			
		||||
            name='date_inited',
 | 
			
		||||
            field=models.DateTimeField(blank=True, null=True, verbose_name='Date initialized'),
 | 
			
		||||
        ),
 | 
			
		||||
        migrations.AddField(
 | 
			
		||||
            model_name='applethost',
 | 
			
		||||
            name='initialized',
 | 
			
		||||
            field=models.BooleanField(default=False, verbose_name='Initialized'),
 | 
			
		||||
        ),
 | 
			
		||||
        migrations.AlterField(
 | 
			
		||||
            model_name='appletpublication',
 | 
			
		||||
            name='applet',
 | 
			
		||||
            field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='publications', to='terminal.applet', verbose_name='Applet'),
 | 
			
		||||
        ),
 | 
			
		||||
        migrations.AlterField(
 | 
			
		||||
            model_name='appletpublication',
 | 
			
		||||
            name='host',
 | 
			
		||||
            field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='publications', to='terminal.applethost', verbose_name='Host'),
 | 
			
		||||
        ),
 | 
			
		||||
        migrations.DeleteModel(
 | 
			
		||||
            name='AppletHostDeployment',
 | 
			
		||||
        ),
 | 
			
		||||
    ]
 | 
			
		||||
| 
						 | 
				
			
			@ -26,6 +26,7 @@ class Applet(JMSBaseModel):
 | 
			
		|||
    protocols = models.JSONField(default=list, verbose_name=_('Protocol'))
 | 
			
		||||
    tags = models.JSONField(default=list, verbose_name=_('Tags'))
 | 
			
		||||
    comment = models.TextField(default='', blank=True, verbose_name=_('Comment'))
 | 
			
		||||
    hosts = models.ManyToManyField(through_fields=('applet', 'host'), through='AppletPublication', to='AppletHost', verbose_name=_('Hosts'))
 | 
			
		||||
 | 
			
		||||
    def __str__(self):
 | 
			
		||||
        return self.name
 | 
			
		||||
| 
						 | 
				
			
			@ -51,8 +52,8 @@ class Applet(JMSBaseModel):
 | 
			
		|||
 | 
			
		||||
 | 
			
		||||
class AppletPublication(JMSBaseModel):
 | 
			
		||||
    applet = models.ForeignKey('Applet', on_delete=models.PROTECT, verbose_name=_('Applet'))
 | 
			
		||||
    host = models.ForeignKey('AppletHost', on_delete=models.PROTECT, verbose_name=_('Host'))
 | 
			
		||||
    applet = models.ForeignKey('Applet', on_delete=models.PROTECT, related_name='publications', verbose_name=_('Applet'))
 | 
			
		||||
    host = models.ForeignKey('AppletHost', on_delete=models.PROTECT, related_name='publications', verbose_name=_('Host'))
 | 
			
		||||
    status = models.CharField(max_length=16, verbose_name=_('Status'))
 | 
			
		||||
    comment = models.TextField(default='', blank=True, verbose_name=_('Comment'))
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,15 +1,17 @@
 | 
			
		|||
from django.db import models
 | 
			
		||||
from django.utils.translation import gettext_lazy as _
 | 
			
		||||
 | 
			
		||||
from common.db.models import JMSBaseModel
 | 
			
		||||
from assets.models import Host
 | 
			
		||||
from ops.ansible import PlaybookRunner, JMSInventory
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
__all__ = ['AppletHost', 'AppletHostDeployment']
 | 
			
		||||
__all__ = ['AppletHost']
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class AppletHost(Host):
 | 
			
		||||
    account_automation = models.BooleanField(default=False, verbose_name=_('Account automation'))
 | 
			
		||||
    initialized = models.BooleanField(default=False, verbose_name=_('Initialized'))
 | 
			
		||||
    date_inited = models.DateTimeField(null=True, blank=True, verbose_name=_('Date initialized'))
 | 
			
		||||
    date_synced = models.DateTimeField(null=True, blank=True, verbose_name=_('Date synced'))
 | 
			
		||||
    status = models.CharField(max_length=16, verbose_name=_('Status'))
 | 
			
		||||
    applets = models.ManyToManyField(
 | 
			
		||||
| 
						 | 
				
			
			@ -17,17 +19,11 @@ class AppletHost(Host):
 | 
			
		|||
        through='AppletPublication',  through_fields=('host', 'applet'),
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    def deploy(self):
 | 
			
		||||
        inventory = JMSInventory([self])
 | 
			
		||||
        playbook = PlaybookRunner(inventory, 'applets.yml')
 | 
			
		||||
        playbook.run()
 | 
			
		||||
 | 
			
		||||
    def __str__(self):
 | 
			
		||||
        return self.name
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class AppletHostDeployment(JMSBaseModel):
 | 
			
		||||
    host = models.ForeignKey('AppletHost', on_delete=models.CASCADE, verbose_name=_('Hosting'))
 | 
			
		||||
    status = models.CharField(max_length=16, verbose_name=_('Status'))
 | 
			
		||||
    comment = models.TextField(default='', blank=True, verbose_name=_('Comment'))
 | 
			
		||||
 | 
			
		||||
    def __str__(self):
 | 
			
		||||
        return self.host
 | 
			
		||||
 | 
			
		||||
    def start(self):
 | 
			
		||||
        pass
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -5,12 +5,12 @@ from common.drf.fields import ObjectRelatedField, LabeledChoiceField
 | 
			
		|||
from common.validators import ProjectUniqueValidator
 | 
			
		||||
from assets.models import Platform
 | 
			
		||||
from assets.serializers import HostSerializer
 | 
			
		||||
from ..models import Applet, AppletPublication, AppletHost, AppletHostDeployment
 | 
			
		||||
from ..models import Applet, AppletPublication, AppletHost
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
__all__ = [
 | 
			
		||||
    'AppletSerializer', 'AppletPublicationSerializer',
 | 
			
		||||
    'AppletHostSerializer', 'AppletHostDeploymentSerializer',
 | 
			
		||||
    'AppletHostSerializer',
 | 
			
		||||
    'AppletUploadSerializer'
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -85,14 +85,3 @@ class AppletHostSerializer(HostSerializer):
 | 
			
		|||
        validators.append(uniq_validator)
 | 
			
		||||
        return validators
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class AppletHostDeploymentSerializer(serializers.ModelSerializer):
 | 
			
		||||
    host = ObjectRelatedField(queryset=AppletHost.objects.all())
 | 
			
		||||
 | 
			
		||||
    class Meta:
 | 
			
		||||
        model = AppletHostDeployment
 | 
			
		||||
        fields_mini = ['id', 'host']
 | 
			
		||||
        read_only_fields = ['date_created', 'date_updated']
 | 
			
		||||
        fields = fields_mini + [
 | 
			
		||||
            'status', 'comment',
 | 
			
		||||
        ] + read_only_fields
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -14,7 +14,7 @@ from common.utils import get_log_keep_day
 | 
			
		|||
from ops.celery.decorator import (
 | 
			
		||||
    register_as_period_task, after_app_ready_start, after_app_shutdown_clean_periodic
 | 
			
		||||
)
 | 
			
		||||
from .models import Status, Session, Command, Task
 | 
			
		||||
from .models import Status, Session, Command, Task, AppletHost
 | 
			
		||||
from .backends import server_replay_storage
 | 
			
		||||
from .utils import find_session_replay_local
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -99,3 +99,9 @@ def upload_session_replay_to_external_storage(session_id):
 | 
			
		|||
    except:
 | 
			
		||||
        pass
 | 
			
		||||
    return
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@shared_task
 | 
			
		||||
def run_applet_host_deployment(did):
 | 
			
		||||
    host = AppletHost.objects.get(id=did)
 | 
			
		||||
    host.deploy()
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -26,8 +26,7 @@ router.register(r'endpoints', api.EndpointViewSet, 'endpoint')
 | 
			
		|||
router.register(r'endpoint-rules', api.EndpointRuleViewSet, 'endpoint-rule')
 | 
			
		||||
router.register(r'applets', api.AppletViewSet, 'applet')
 | 
			
		||||
router.register(r'applet-hosts', api.AppletHostViewSet, 'applet-host')
 | 
			
		||||
router.register(r'applet-publication', api.AppletPublicationViewSet, 'applet-publication')
 | 
			
		||||
router.register(r'applet-host-deployment', api.AppletHostDeploymentViewSet, 'applet-host-deployment')
 | 
			
		||||
router.register(r'applet-publications', api.AppletPublicationViewSet, 'applet-publication')
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
urlpatterns = [
 | 
			
		||||
| 
						 | 
				
			
			@ -46,10 +45,6 @@ urlpatterns = [
 | 
			
		|||
    path('command-storages/<uuid:pk>/test-connective/', api.CommandStorageTestConnectiveApi.as_view(), name='command-storage-test-connective'),
 | 
			
		||||
    # components
 | 
			
		||||
    path('components/metrics/', api.ComponentsMetricsAPIView.as_view(), name='components-metrics'),
 | 
			
		||||
    # v2: get session's replay
 | 
			
		||||
    # path('v2/sessions/<uuid:pk>/replay/',
 | 
			
		||||
    #     api.SessionReplayV2ViewSet.as_view({'get': 'retrieve'}),
 | 
			
		||||
    #     name='session-replay-v2'),
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
old_version_urlpatterns = [
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue