From 3a2d8ba13f7317bdd9d7fab5e2015bf87094063c Mon Sep 17 00:00:00 2001 From: wb-msm261421 Date: Fri, 5 Aug 2022 17:10:40 +0800 Subject: [PATCH 1/3] =?UTF-8?q?channel=E6=A8=A1=E5=9D=97=E4=BC=98=E5=8C=96?= =?UTF-8?q?,=20ssh=E9=80=9A=E9=81=93=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- sysom_api/apps/channel/channels/base.py | 6 +- sysom_api/apps/channel/channels/ssh.py | 74 ++++++++++++++++++++++--- sysom_api/apps/channel/views.py | 26 +++------ sysom_api/apps/host/views.py | 4 +- sysom_api/apps/task/executors.py | 6 +- 5 files changed, 79 insertions(+), 37 deletions(-) diff --git a/sysom_api/apps/channel/channels/base.py b/sysom_api/apps/channel/channels/base.py index 8d72a227..03f3c27d 100644 --- a/sysom_api/apps/channel/channels/base.py +++ b/sysom_api/apps/channel/channels/base.py @@ -1,13 +1,13 @@ """ 通道Base + +多通道是以单文件的方式构成,文件名就是通道名称(例如: ssh.py 为ssh通道), 通道 +文件中实现Channel类, 继承BaseChannel类, 必须实现client方法, run_command方法 """ from abc import ABCMeta, abstractmethod class BaseChannel(metaclass=ABCMeta): - def __init__(self, **kwargs) -> None: - self.connect_args = kwargs - self._client = self.client(**kwargs) @abstractmethod def client(self, **kwargs): diff --git a/sysom_api/apps/channel/channels/ssh.py b/sysom_api/apps/channel/channels/ssh.py index 6e1c56c8..bcc2684d 100644 --- a/sysom_api/apps/channel/channels/ssh.py +++ b/sysom_api/apps/channel/channels/ssh.py @@ -3,12 +3,17 @@ import logging import paramiko from io import StringIO from paramiko.client import SSHClient, AutoAddPolicy -from paramiko.rsakey import RSAKey +from paramiko.rsakey import RSAKey +from django.db import connection from .base import BaseChannel -from ..models import SettingsModel +from ..models import SettingsModel, ExecuteResult +from lib.exception import APIException +from lib.utils import uuid_8 + + +logger = logging.getLogger(__name__) -__all__ = ['SSH'] class ChannelError(paramiko.AuthenticationException): def __init__(self, code=400, message='后端异常', args=('后端异常',)) -> None: @@ -20,7 +25,7 @@ class ChannelError(paramiko.AuthenticationException): return self.message -class SSHChannel(BaseChannel): +class SSH: """ args: - hostname 主机IP (必填) @@ -40,7 +45,9 @@ class SSHChannel(BaseChannel): if 'password' in kwargs: self.connect_args['password'] = kwargs.get('password') else: - self.connect_args['pkey'] = RSAKey.from_private_key(StringIO(self.get_ssh_key()['private_key'])) + self.connect_args['pkey'] = RSAKey.from_private_key( + StringIO(self.get_ssh_key()['private_key'])) + self._client: SSHClient = self.client() def client(self): @@ -55,7 +62,7 @@ class SSHChannel(BaseChannel): raise Exception('authorization fail!') @classmethod - def get_ssh_key(self) -> dict: + def get_ssh_key(cls) -> dict: instance = SettingsModel.objects.get(key='ssh_key') return json.loads(instance.value) @@ -76,15 +83,64 @@ class SSHChannel(BaseChannel): command = f'mkdir -p -m 700 ~/.ssh && \ echo {public_key!r} >> ~/.ssh/authorized_keys && \ chmod 600 ~/.ssh/authorized_keys' - statue, _ = self.run_command(command) + statue, _ = self.run_command(command) if statue != 0: raise Exception('add public key faild!') @staticmethod - def validate_ssh_host(ip: str, password: str, port: int=22, username: str='root'): + def validate_ssh_host(ip: str, password: str, port: int = 22, username: str = 'root'): try: - ssh = SSHChannel(hostname=ip, password=password, port=port, username=username, timeout=2) + ssh = SSH(hostname=ip, password=password, + port=port, username=username, timeout=2) ssh.add_public_key() return True, 'authorization success' except Exception as e: return False, f'error: {e}' + + +class Channel(BaseChannel): + FIELDS = ('instance', 'cmd') + + def __init__(self, *args, **kwargs) -> None: + self.kwargs = kwargs + self.ssh = None + self.shell_script = None + + self.validate_kwargs() + + def validate_kwargs(self): + for item in filter( + lambda x: not x[1], [(field, self.kwargs.get(field, None)) + for field in self.FIELDS] + ): + raise APIException(message=f'parameter: {item[0]} not found!') + + if not self.ssh: + self.ssh = SSH(hostname=self.kwargs['instance']) + self.shell_script = self.kwargs['cmd'] + + def client(self): + return self.ssh if self.ssh else SSH(hostname=self.kwargs['instance']) + + def run_command(self): + kwargs = dict() + task_id = uuid_8() + kwargs['task_id'] = task_id + + status, res = self.ssh.run_command(self.shell_script) + kwargs['result'] = {'state': status, 'result': res} + + self._save_execute_result(kwargs) + return { + 'task_id': task_id, + 'state': status + } + + @classmethod + def _save_execute_result(cls, kwargs): + try: + ExecuteResult.objects.create(**kwargs) + except Exception as e: + raise APIException(message=str(e)) + finally: + connection.close() diff --git a/sysom_api/apps/channel/views.py b/sysom_api/apps/channel/views.py index e377d201..777399ff 100644 --- a/sysom_api/apps/channel/views.py +++ b/sysom_api/apps/channel/views.py @@ -1,10 +1,10 @@ import logging import re +from importlib import import_module from rest_framework.viewsets import GenericViewSet from .models import ExecuteResult from lib.response import other_response -from lib.utils import uuid_8 -from .channels.ssh import SSHChannel + logger = logging.getLogger(__name__) @@ -23,23 +23,11 @@ class ChannelAPIView(GenericViewSet): def channel_post(self, request, *args, **kwargs): data = getattr(request, 'data') - result = dict() - channel = data.get('channel', None) - if channel is None or channel == 'ssh': - res, msg = self.validate_ssh_channel_parame(data) - if not res: - return other_response(code=400, message=msg, success=False) - try: - ssh = SSHChannel(hostname=data['instance']) - state, res = ssh.run_command(data['cmd']) - task_id = uuid_8() - ExecuteResult.objects.create(task_id=task_id, result={'state': state, 'result': res}) - result['task_id'] = task_id - result['state'] = state - except Exception as e: - return other_response(code=400, message=f'Error: {e}', success=False) - else: - return other_response(code=400, message='channel 不存在!', success=False) + channel_type = data.pop('channel', 'ssh') + package = import_module(f'apps.channel.channels.{channel_type}') + + channel = package.Channel(**data) + result = channel.run_command() return other_response(result=result, message='操作成功') def validate_ssh_channel_parame(self, data): diff --git a/sysom_api/apps/host/views.py b/sysom_api/apps/host/views.py index cd081f33..e10bcbb8 100644 --- a/sysom_api/apps/host/views.py +++ b/sysom_api/apps/host/views.py @@ -25,7 +25,7 @@ from lib.utils import HTTP from lib.validates import validate_ssh from concurrent.futures import ThreadPoolExecutor, as_completed from apps.alarm.views import _create_alarm_message -from apps.channel.channels.ssh import SSHChannel +from apps.channel.channels.ssh import SSH logger = logging.getLogger(__name__) @@ -115,7 +115,7 @@ class HostModelViewSet(CommonModelViewSet, instance.save() def _validate_and_initialize_host(self, context): - s, e = SSHChannel.validate_ssh_host( + s, e = SSH.validate_ssh_host( ip=context['ip'], password=context['host_password'], username=context['username'], diff --git a/sysom_api/apps/task/executors.py b/sysom_api/apps/task/executors.py index ecc94d3d..fd9afdc7 100644 --- a/sysom_api/apps/task/executors.py +++ b/sysom_api/apps/task/executors.py @@ -3,7 +3,6 @@ import os import socket import subprocess import logging - from apps.task.models import JobModel from django.conf import settings from django.db import connection @@ -60,7 +59,6 @@ class SshJob: host_ips.append(ip) status_code, result = Channel.post_channel(data=script, token=self.user['token']) - if status_code == 200: task_id = result.get('task_id') self.update_job(task_id=task_id) @@ -121,7 +119,7 @@ class SshJob: def _import_service(self): from apps.host.models import HostModel - from apps.channel.channels.ssh import SSHChannel + from apps.channel.channels.ssh import SSH try: self.update_job(status="Running") @@ -143,7 +141,7 @@ class SshJob: self.update_job(status="Fail", result="host not found by script return IP:%s" % ip) break - ssh_cli = SSHChannel( + ssh_cli = SSH( hostname=host.ip, port=host.port, username=host.username) status, result = ssh_cli.run_command(cmd) -- Gitee From 14d2df9a300b7e89ab40f161e00552663f2220c8 Mon Sep 17 00:00:00 2001 From: wb-msm261421 Date: Mon, 8 Aug 2022 11:55:09 +0800 Subject: [PATCH 2/3] =?UTF-8?q?channel=20view.py=20post=E8=AF=B7=E6=B1=82?= =?UTF-8?q?=E4=BB=A3=E7=A0=81=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- sysom_api/apps/channel/views.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sysom_api/apps/channel/views.py b/sysom_api/apps/channel/views.py index 777399ff..8fc6226c 100644 --- a/sysom_api/apps/channel/views.py +++ b/sysom_api/apps/channel/views.py @@ -4,7 +4,7 @@ from importlib import import_module from rest_framework.viewsets import GenericViewSet from .models import ExecuteResult from lib.response import other_response - +from .channels.ssh import SSH logger = logging.getLogger(__name__) @@ -70,7 +70,7 @@ class ChannelAPIView(GenericViewSet): if 'port' in data: kwargs['port'] = data.get('port') - s, m = SSHChannel.validate_ssh_host(**kwargs) + s, m = SSH.validate_ssh_host(**kwargs) if s: return other_response(message=m) else: -- Gitee From ad9f73739dbbca177fd4ae85766563368de2324c Mon Sep 17 00:00:00 2001 From: wb-msm261421 Date: Mon, 8 Aug 2022 16:24:51 +0800 Subject: [PATCH 3/3] =?UTF-8?q?consumer=E4=BF=AE=E6=94=B9ssh=E7=B1=BB?= =?UTF-8?q?=E5=90=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- sysom_api/consumer/consumers.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sysom_api/consumer/consumers.py b/sysom_api/consumer/consumers.py index 6bf07efc..9a3dbbab 100644 --- a/sysom_api/consumer/consumers.py +++ b/sysom_api/consumer/consumers.py @@ -43,7 +43,7 @@ class SshConsumer(WebsocketConsumer): def _connect_host_init(self): """初始化host连接""" from apps.host.models import HostModel - from apps.channel.channels.ssh import SSHChannel + from apps.channel.channels.ssh import SSH instance = get_host_instance(model=HostModel, ip=self.host_ip, created_by=self.user.id) if not instance: self.send(bytes_data=b'Not Found host / No Permission\r\n') @@ -55,7 +55,7 @@ class SshConsumer(WebsocketConsumer): self.send(bytes_data=b'Connecting ...\r\n') try: # self.ssh = self.host.get_host_client().get_client() - self.ssh = SSHChannel(hostname=instance.ip, username=instance.username, port=instance.port)._client + self.ssh = SSH(hostname=instance.ip, username=instance.username, port=instance.port)._client except Exception as e: self.send(bytes_data=f'Exception: {e}\r\n'.encode()) self.close() -- Gitee