pref: 修改 applet host deploy

pull/9008/head
ibuler 2022-10-28 18:19:44 +08:00
parent 8f9eb64c8d
commit 12b74093e2
12 changed files with 140 additions and 54 deletions

View File

@ -9,8 +9,9 @@ __all__ = ['JMSInventory']
class JMSInventory: class JMSInventory:
def __init__(self, manager, assets=None, account_policy='smart', def __init__(self, assets, account_policy='smart',
account_prefer='root,administrator', host_callback=None): account_prefer='root,administrator',
host_callback=None):
""" """
:param assets: :param assets:
:param account_prefer: account username name if not set use account_policy :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_matched = list(filter(lambda x: x.name == 'ssh', protocols))
ssh_protocol = ssh_protocol_matched[0] if ssh_protocol_matched else None ssh_protocol = ssh_protocol_matched[0] if ssh_protocol_matched else None
host['ansible_host'] = asset.address host['ansible_host'] = asset.address
if asset.port == 0: host['ansible_port'] = ssh_protocol.port if ssh_protocol else 22
host['ansible_port'] = ssh_protocol.port if ssh_protocol else 22
else:
host['ansible_port'] = asset.port
su_from = account.su_from su_from = account.su_from
if platform.su_enabled and su_from: if platform.su_enabled and su_from:

View File

@ -99,7 +99,7 @@ class CeleryPeriodTaskViewSet(CommonApiMixin, viewsets.ModelViewSet):
class CeleryTaskViewSet(CommonApiMixin, viewsets.ReadOnlyModelViewSet): 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 serializer_class = CeleryTaskSerializer
http_method_names = ('get', 'head', 'options',) http_method_names = ('get', 'head', 'options',)

View File

@ -1,7 +1,7 @@
import os.path
import shutil import shutil
import zipfile import zipfile
import yaml import yaml
import os.path
from django.core.files.storage import default_storage from django.core.files.storage import default_storage
from rest_framework import viewsets from rest_framework import viewsets
@ -9,12 +9,16 @@ from rest_framework.decorators import action
from rest_framework.response import Response from rest_framework.response import Response
from rest_framework.serializers import ValidationError 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 from terminal.serializers import AppletUploadSerializer
__all__ = ['AppletViewSet', 'AppletPublicationViewSet']
class AppletViewSet(viewsets.ModelViewSet): class AppletViewSet(viewsets.ModelViewSet):
queryset = models.Applet.objects.all() queryset = Applet.objects.all()
serializer_class = serializers.AppletSerializer serializer_class = serializers.AppletSerializer
rbac_perms = { rbac_perms = {
'upload': 'terminal.add_applet', 'upload': 'terminal.add_applet',
@ -67,7 +71,7 @@ class AppletViewSet(viewsets.ModelViewSet):
name = manifest['name'] name = manifest['name']
update = request.query_params.get('update') 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: if instance and not update:
return Response({'error': 'Applet already exists: {}'.format(name)}, status=400) return Response({'error': 'Applet already exists: {}'.format(name)}, status=400)
@ -82,5 +86,5 @@ class AppletViewSet(viewsets.ModelViewSet):
class AppletPublicationViewSet(viewsets.ModelViewSet): class AppletPublicationViewSet(viewsets.ModelViewSet):
queryset = models.AppletPublication.objects.all() queryset = AppletPublication.objects.all()
serializer_class = serializers.AppletPublicationSerializer serializer_class = serializers.AppletPublicationSerializer

View File

@ -1,23 +1,35 @@
from rest_framework import viewsets 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 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): class AppletHostViewSet(viewsets.ModelViewSet):
serializer_class = serializers.AppletHostSerializer serializer_class = serializers.AppletHostSerializer
def get_queryset(self): def get_queryset(self):
return models.AppletHost.objects.all() return AppletHost.objects.all()
def dispatch(self, request, *args, **kwargs): def dispatch(self, request, *args, **kwargs):
with tmp_to_builtin_org(system=1): with tmp_to_builtin_org(system=1):
return super().dispatch(request, *args, **kwargs) 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): @action(methods=['get'], detail=True, url_path='')
queryset = models.AppletHostDeployment.objects.all() def not_published_applets(self, request, *args, **kwargs):
serializer_class = serializers.AppletHostDeploymentSerializer instance = self.get_object()
applets = Applet.objects.exclude(id__in=instance.applets.all())
serializer = serializers.AppletSerializer(applets, many=True)
return Response(serializer.data)

View File

@ -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)

View File

@ -1,6 +1,6 @@
--- ---
- hosts: windows - hosts: all
vars: vars:
- DownloadHost: https://demo.jumpserver.org/download - DownloadHost: https://demo.jumpserver.org/download
- RDS_Licensing: enabled - RDS_Licensing: enabled

View File

@ -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',
),
]

View File

@ -26,6 +26,7 @@ class Applet(JMSBaseModel):
protocols = models.JSONField(default=list, verbose_name=_('Protocol')) protocols = models.JSONField(default=list, verbose_name=_('Protocol'))
tags = models.JSONField(default=list, verbose_name=_('Tags')) tags = models.JSONField(default=list, verbose_name=_('Tags'))
comment = models.TextField(default='', blank=True, verbose_name=_('Comment')) 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): def __str__(self):
return self.name return self.name
@ -51,8 +52,8 @@ class Applet(JMSBaseModel):
class AppletPublication(JMSBaseModel): class AppletPublication(JMSBaseModel):
applet = models.ForeignKey('Applet', on_delete=models.PROTECT, verbose_name=_('Applet')) applet = models.ForeignKey('Applet', on_delete=models.PROTECT, related_name='publications', verbose_name=_('Applet'))
host = models.ForeignKey('AppletHost', on_delete=models.PROTECT, verbose_name=_('Host')) host = models.ForeignKey('AppletHost', on_delete=models.PROTECT, related_name='publications', verbose_name=_('Host'))
status = models.CharField(max_length=16, verbose_name=_('Status')) status = models.CharField(max_length=16, verbose_name=_('Status'))
comment = models.TextField(default='', blank=True, verbose_name=_('Comment')) comment = models.TextField(default='', blank=True, verbose_name=_('Comment'))

View File

@ -1,15 +1,17 @@
from django.db import models from django.db import models
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from common.db.models import JMSBaseModel
from assets.models import Host from assets.models import Host
from ops.ansible import PlaybookRunner, JMSInventory
__all__ = ['AppletHost', 'AppletHostDeployment'] __all__ = ['AppletHost']
class AppletHost(Host): class AppletHost(Host):
account_automation = models.BooleanField(default=False, verbose_name=_('Account automation')) 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')) date_synced = models.DateTimeField(null=True, blank=True, verbose_name=_('Date synced'))
status = models.CharField(max_length=16, verbose_name=_('Status')) status = models.CharField(max_length=16, verbose_name=_('Status'))
applets = models.ManyToManyField( applets = models.ManyToManyField(
@ -17,17 +19,11 @@ class AppletHost(Host):
through='AppletPublication', through_fields=('host', 'applet'), through='AppletPublication', through_fields=('host', 'applet'),
) )
def deploy(self):
inventory = JMSInventory([self])
playbook = PlaybookRunner(inventory, 'applets.yml')
playbook.run()
def __str__(self): def __str__(self):
return self.name 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

View File

@ -5,12 +5,12 @@ from common.drf.fields import ObjectRelatedField, LabeledChoiceField
from common.validators import ProjectUniqueValidator from common.validators import ProjectUniqueValidator
from assets.models import Platform from assets.models import Platform
from assets.serializers import HostSerializer from assets.serializers import HostSerializer
from ..models import Applet, AppletPublication, AppletHost, AppletHostDeployment from ..models import Applet, AppletPublication, AppletHost
__all__ = [ __all__ = [
'AppletSerializer', 'AppletPublicationSerializer', 'AppletSerializer', 'AppletPublicationSerializer',
'AppletHostSerializer', 'AppletHostDeploymentSerializer', 'AppletHostSerializer',
'AppletUploadSerializer' 'AppletUploadSerializer'
] ]
@ -85,14 +85,3 @@ class AppletHostSerializer(HostSerializer):
validators.append(uniq_validator) validators.append(uniq_validator)
return validators 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

View File

@ -14,7 +14,7 @@ from common.utils import get_log_keep_day
from ops.celery.decorator import ( from ops.celery.decorator import (
register_as_period_task, after_app_ready_start, after_app_shutdown_clean_periodic 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 .backends import server_replay_storage
from .utils import find_session_replay_local from .utils import find_session_replay_local
@ -99,3 +99,9 @@ def upload_session_replay_to_external_storage(session_id):
except: except:
pass pass
return return
@shared_task
def run_applet_host_deployment(did):
host = AppletHost.objects.get(id=did)
host.deploy()

View File

@ -26,8 +26,7 @@ router.register(r'endpoints', api.EndpointViewSet, 'endpoint')
router.register(r'endpoint-rules', api.EndpointRuleViewSet, 'endpoint-rule') router.register(r'endpoint-rules', api.EndpointRuleViewSet, 'endpoint-rule')
router.register(r'applets', api.AppletViewSet, 'applet') router.register(r'applets', api.AppletViewSet, 'applet')
router.register(r'applet-hosts', api.AppletHostViewSet, 'applet-host') router.register(r'applet-hosts', api.AppletHostViewSet, 'applet-host')
router.register(r'applet-publication', api.AppletPublicationViewSet, 'applet-publication') router.register(r'applet-publications', api.AppletPublicationViewSet, 'applet-publication')
router.register(r'applet-host-deployment', api.AppletHostDeploymentViewSet, 'applet-host-deployment')
urlpatterns = [ urlpatterns = [
@ -46,10 +45,6 @@ urlpatterns = [
path('command-storages/<uuid:pk>/test-connective/', api.CommandStorageTestConnectiveApi.as_view(), name='command-storage-test-connective'), path('command-storages/<uuid:pk>/test-connective/', api.CommandStorageTestConnectiveApi.as_view(), name='command-storage-test-connective'),
# components # components
path('components/metrics/', api.ComponentsMetricsAPIView.as_view(), name='components-metrics'), 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 = [ old_version_urlpatterns = [