U api add host/setting app

pull/22/head
雷二猛 2019-11-18 21:04:03 +08:00
parent 0b36aaf6c1
commit bad6431553
11 changed files with 149 additions and 5 deletions

View File

View File

@ -0,0 +1,28 @@
from django.db import models
from libs import ModelMixin, human_time
from apps.account.models import User
from libs.ssh import SSH
class Host(models.Model, ModelMixin):
name = models.CharField(max_length=50)
zone = models.CharField(max_length=50)
hostname = models.CharField(max_length=50)
port = models.IntegerField()
username = models.CharField(max_length=50)
desc = models.CharField(max_length=255, null=True)
created_at = models.CharField(max_length=20, default=human_time)
created_by = models.ForeignKey(User, models.PROTECT, related_name='+')
deleted_at = models.CharField(max_length=20, null=True)
deleted_by = models.ForeignKey(User, models.PROTECT, related_name='+', null=True)
def get_ssh(self, pkey):
return SSH(self.hostname, self.port, self.username, pkey)
def __repr__(self):
return '<Host %r>' % self.name
class Meta:
db_table = 'hosts'
ordering = ('-id',)

View File

@ -0,0 +1,7 @@
from django.conf.urls import url
from .views import *
urlpatterns = [
url(r'^$', HostView.as_view()),
]

View File

@ -0,0 +1,70 @@
from django.views.generic import View
from libs import json_response, JsonParser, Argument
from apps.setting.utils import AppSetting
from apps.host.models import Host
from libs.ssh import SSH, AuthenticationException
from libs import human_time
class HostView(View):
def get(self, request):
hosts = Host.objects.filter(deleted_by_id__isnull=True)
return json_response(hosts)
def post(self, request):
form, error = JsonParser(
Argument('id', type=int, required=False),
Argument('zone', help='请输入主机类型'),
Argument('name', help='请输主机名称'),
Argument('username', help='请输入登录用户名'),
Argument('hostname', help='请输入主机名或IP'),
Argument('port', type=int, help='请输入SSH端口'),
Argument('desc', required=False),
Argument('password', required=False),
).parse(request.body)
if error is None:
if valid_ssh(form.hostname, form.port, form.username, form.pop('password')) is False:
return json_response('auth fail')
if form.id:
Host.objects.filter(pk=form.pop('id')).update(**form)
else:
form.created_by = request.user
Host.objects.create(**form)
return json_response(error=error)
def delete(self, request):
form, error = JsonParser(
Argument('id', type=int, help='请指定操作对象')
).parse(request.GET)
if error is None:
Host.objects.filter(pk=form.id).update(
deleted_at=human_time(),
deleted_by=request.user,
)
return json_response(error=error)
def valid_ssh(hostname, port, username, password):
try:
private_key = AppSetting.get('private_key')
public_key = AppSetting.get('public_key')
except KeyError:
private_key, public_key = SSH.generate_key()
AppSetting.set('private_key', private_key, 'ssh private key')
AppSetting.set('public_key', public_key, 'ssh public key')
if password:
cli = SSH(hostname, port, username, password=password)
code, stdout, stderr = cli.exec_command('mkdir -p -m 700 ~/.ssh && \
echo %r >> ~/.ssh/authorized_keys && \
chmod 600 ~/.ssh/authorized_keys' % public_key)
if code != 0:
raise Exception('add public key error: ' + ''.join(x for x in stderr))
else:
cli = SSH(hostname, port, username, private_key)
try:
cli.ping()
except AuthenticationException:
return False
return True

View File

View File

@ -0,0 +1,13 @@
from django.db import models
class Setting(models.Model):
key = models.CharField(max_length=50, unique=True)
value = models.TextField()
desc = models.CharField(max_length=255, null=True)
def __repr__(self):
return '<Setting %r>' % self.key
class Meta:
db_table = 'settings'

View File

@ -0,0 +1,21 @@
from functools import lru_cache
from apps.setting.models import Setting
class AppSetting:
keys = ('public_key', 'private_key')
@classmethod
@lru_cache(maxsize=64)
def get(cls, key):
info = Setting.objects.filter(key=key).first()
if not info:
raise KeyError(f'no such key for {key!r}')
return info.value
@classmethod
def set(cls, key, value, desc=None):
if key in cls.keys:
Setting.objects.update_or_create(key=key, defaults={'value': value, 'desc': desc})
else:
raise KeyError('invalid key')

View File

@ -1,20 +1,21 @@
from paramiko.client import SSHClient, AutoAddPolicy from paramiko.client import SSHClient, AutoAddPolicy
from paramiko.config import SSH_PORT from paramiko.config import SSH_PORT
from paramiko.rsakey import RSAKey from paramiko.rsakey import RSAKey
from paramiko.ssh_exception import AuthenticationException
from io import StringIO from io import StringIO
class SSH: class SSH:
def __init__(self, host, port=SSH_PORT, username='root', pkey=None, password=None, connect_timeout=10): def __init__(self, hostname, port=SSH_PORT, username='root', pkey=None, password=None, connect_timeout=10):
if pkey is None and password is None: if pkey is None and password is None:
raise Exception('public key and password must have one is not None') raise Exception('public key and password must have one is not None')
self.client = None self.client = None
self.arguments = { self.arguments = {
'hostname': host, 'hostname': hostname,
'port': port, 'port': port,
'username': username, 'username': username,
'password': password, 'password': password,
'pkey': pkey, 'pkey': RSAKey.from_private_key(StringIO(pkey)) if isinstance(pkey, str) else pkey,
'timeout': connect_timeout, 'timeout': connect_timeout,
} }

View File

@ -1,2 +1,3 @@
Django==2.2.7 Django==2.2.7
channels==2.3.1 channels==2.3.1
paramiko==2.6.0

View File

@ -31,6 +31,8 @@ ALLOWED_HOSTS = []
INSTALLED_APPS = [ INSTALLED_APPS = [
'channels', 'channels',
'apps.account', 'apps.account',
'apps.host',
'apps.setting',
] ]
MIDDLEWARE = [ MIDDLEWARE = [

View File

@ -16,5 +16,6 @@ Including another URLconf
from django.urls import path, include from django.urls import path, include
urlpatterns = [ urlpatterns = [
path('account/', include('apps.account.urls')) path('account/', include('apps.account.urls')),
path('host/', include('apps.host.urls')),
] ]