簡單分析實現運維利器---批量操作bashshell

背景

爲了進一步完善自己寫的小運維繫統,今天就繼續來補充一個批量操作bashshell,並記錄操作用於審計!

 

一、思路

實現批量bashshell操作思路其實挺簡單,同樣是用到paramiko庫,如果只寫小腳本實現的可以參考我的另一篇文章Python

搞定繁瑣運維之批量執行Linux命令,如果運用在web應用上,則需要websocket的幫助。

具體思路:

.打開websocket通道2.打開ssh通道3.執行shell4.反饋執行結果.5.保存操作記錄

這個流程在Django運維繫統基礎功能之—web遠程ssh終端這篇文章也講述的很清楚。

但是我在想,實現批量操作需不需要做到像web終端一樣?所有數據都要返回?我的答案是否定的,因爲我覺得,要實行批量操作的時候,一般都只是做配置、備份、部署應用等。

因此只需要做到標準輸入->標準輸出或者錯誤輸出即可。

 

二、實現

其實有上面的思路以及上述的兩篇文章已經把細節講的非常清楚了,在這裏就不過多的闡述了,不清楚的可以看看上面提到的兩篇文章,直接上代碼供大家參考吧。

def batch(request):
    global hostnum, hostip, hostport, hostuser, hostpass
    if request.session.get('login')==None:
        return redirect('/sys/login/')
    if not request.is_websocket():
        dataa = models.host.objects.all()
        hostlist = (request.POST.get(f'{data.hostaddress}') for data in dataa)
        hostnum = 0
        hostip = []
        hostport = []
        hostuser = []
        hostpass = []
        for data in dataa:
            host = request.POST.get(f'{data.hostaddress}')
            if host != None:
                hostnum = hostnum + 1
                hostip.append(host)
                hostport.append(models.host.objects.get(hostaddress=host).hostport)
                hostuser.append(models.host.objects.get(hostaddress=host).hostuser)
                hostpass.append(models.host.objects.get(hostaddress=host).hostpass)
        return render(request,'html/batch.html',locals())
    else:
        if hostnum == 0 :
            nummessage = '沒有選擇主機.'
            request.websocket.send(nummessage.encode('utf-8'))
        else:
            wamessage = '等待主機連接ing...'
            request.websocket.send(wamessage.encode('utf-8'))
        a = []
        b = []
        c = []
        d = []
        for i in range(hostnum):
            a.append('client' + str(i))
            b.append('stdin' + str(i))
            c.append('stdout' + str(i))
            d.append('stderr' + str(i))
            a[i] = paramiko.SSHClient()  # 創建num個連接對象
            # client2 = paramiko.SSHClient()
            a[i].set_missing_host_key_policy(paramiko.AutoAddPolicy)  # 添加num個主機名及主機密鑰到本地HostKeys對象
            # client2.set_missing_host_key_policy(paramiko.AutoAddPolicy)
            try:
                a[i].connect(hostname=hostip[i], port=hostport[i], username=hostuser[i], password=hostpass[i])  # num個連接
                # client2.connect(hostname=ip[1],port=port[1],username=username[1],password=password[1])
                print(f'主機{hostip[i]}連接成功!')
                message = f'主機{hostip[i]}連接成功!'
                request.websocket.send(message.encode('utf-8'))
            except:
                print(f'主機{hostip[i]}連接失敗,請確認列表信息!')
                message = f'主機{hostip[i]}連接失敗,請確認列表信息!'
                request.websocket.send(message.encode('utf-8'))

        for shell in request.websocket:
            addactionshell = models.batchaction()
            addactionshell.hostaddress = hostip
            addactionshell.username = request.session['username']
            addactionshell.actionshell = shell.decode('utf-8')
            addactionshell.starttime = time.strftime("%Y%m%d%H%M%S")
            addactionshell.save()
            for i in range(hostnum):
                b[i], c[i], d[i] = a[i].exec_command(shell)  # num個對象執行e命令
                # print(f'----------------------{hostip[i]}執行結果----------------------')
                # print(c[i].read().decode('utf-8'))  # 打印正確輸出
                # print(d[i].read().decode('utf-8'))  # 打印錯誤輸出
                output = '-'*97 + f'{hostip[i]}執行結果' + '-'*90 + c[i].read().decode('utf-8')  + d[i].read().decode('utf-8')
                request.websocket.send(output.encode('utf-8'))

 

html頁面:

{% extends 'base.html' %}
{% load static %}
{% block title %}批量執行{% endblock %}
{% block css %}
     <link rel="stylesheet" href="{% static 'adminlet-2.4.10/bower_components/datatables.net-bs/css/dataTables.bootstrap.css' %}">
    <link rel="stylesheet" href="{% static 'asciinemaplayer/asciinema-player.css' %}">
{% endblock %}

<!-- 頂部內容  -->
{% block breadcrumb %}
    <!-- Content Header (Page header) -->
    <section class="content-header">
      <h1>批量執行</h1>
    </section>
{% endblock %}

<!-- 身體內容  -->
{% block content %}
<div class="col-md-4">
    <button type="button" style="float: left" class="btn btn-default" data-toggle="modal" data-target="#selecthost">選擇主機</button>
</div>
    <!-- Modal選擇主機模態框 -->
<div class="modal fade bs-example-modeal-sm" id="selecthost" tabindex="-1" role="dialog" aria-labelledby="selecthostLabel">
  <div class="modal-dialog modal-sm" role="document">
    <div class="modal-content">
      <div class="modal-header">
        <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
        <h4 class="modal-title" id="selecthostlLabel">請選擇主機</h4>
      </div>
<form action="{% url 'batch' %}" method="post">
      {% csrf_token %}
      <div class="modal-body">
      <table class="table">
      <tbody>
              {% for data in dataa %}
                  <tr>
                      <td>
                          <label class="checkbox-inline">
                              <input type="checkbox" name="{{data.hostaddress}}" value="{{data.hostaddress}}">{{ data.hostaddress }}
                          </label>
                      </td>
                  </tr>
            {% endfor %}
      </tbody>
      </table>

      </div>

      <div class="modal-footer">
        <button type="button" class="btn btn-default" data-dismiss="modal">關閉</button>
        <button type="submit" class="btn btn-primary" id="selhostip">確定</button>
      </div>
    </form>
    </div>
  </div>
</div>
<br><br>

    <!-- 批量連接box -->
    <div class="col-md-4">
<div class="box box-info" >
        <div class="box-header with-border">
          <h3 class="box-title">執行的主機</h3>
        </div>
        <div class="box-body">
        <table>
            {% for i in hostlist  %}
                {%  if i != None %}
                    <tr>
                        <td>{{i}}</td>
                    </tr>
                {% endif %}
            {% endfor %}
        </table>
        </div>
            <div class="box-footer">
               <a style="float: right" class="btn btn-success" id="conwebsocket">連接</a>
        </div>
      </div>
                          <label class="form-inline" >執行的命令:
            <input class="form-control" type="text" name="acshell" id="shell" value=''>
            <a class="btn btn-success" id="sendshell">確定</a><br>(只支持標準輸入-標準輸出-標準錯誤模式)
                    </label>
</div>

<div class="col-md-8">
<div class="box box-info" >
        <div class="box-header with-border">
          <h3 class="box-title">執行結果</h3>
        </div>
        <div class="box-body" id="showmess">

        </div>
      </div>
</div>


{% endblock %}

<!-- JS內容  -->
{% block script %}
<script src="{% static 'adminlet-2.4.10/bower_components/datatables.net/js/jquery.dataTables.min.js' %}"></script>
<script src="{% static 'adminlet-2.4.10/bower_components/datatables.net-bs/js/dataTables.bootstrap.min.js' %}"></script>
    <script src="/static/xterm_/jquery.js"></script>
    <script type="text/javascript">
    $(function () {
        $('#conwebsocket').click(function () {
            if (window.s) {
                window.s.close()
            }
            /*創建socket連接*/
            var socket = new WebSocket("ws://" + window.location.host + "{% url 'batch' %}");
            socket.onopen = function () {
                console.log('WebSocket open');//成功連接上Websocket
            };
            socket.onmessage = function (mess) {
                console.log('message: ' + mess.data);//打印出服務端返回過來的數據
                $('#showmess').prepend('<p>' + mess.data + '</p>');
            };
            // Call onopen directly if socket is already open
            if (socket.readyState == WebSocket.OPEN) socket.onopen();
            window.s = socket;
        });
        $('#sendshell').click(function () {
            //如果未連接到websocket
            if (!window.s) {
                $('#showmess').prepend('沒有連接主機');
            } else {
                window.s.send($('#shell').val());//通過websocket發送數據
            }
        });
        $('#close_websocket').click(function () {
            if (window.s) {
                window.s.close();//關閉websocket
                console.log('websocket已關閉');
            }
        });

    });
    </script>
{% endblock %}

 

models:

class batchaction(models.Model):
    hostaddress = models.CharField(max_length=255)
    username = models.CharField(max_length=255)
    actionshell = models.CharField(max_length=255)
    starttime = models.CharField(max_length=255)

 

 

審計html,在錄像的頁面基礎上添加:

  <!-- datatable 批量操作審計-->
  <div class="box box-default">

    <div class="box-header with-border">
      <h3 class="box-title">批量操作列表</h3>
    </div>

    <div class="box-body">
<table id="batchlist" class="display" style="width:100%">
                <thead>
        <tr>
          <th>主機地址</th>
          <th>操作人</th>
          <th>執行shell</th>
          <th>開始時間</th>
        </tr>
        </thead>
        <tbody>
      {% for batchacction in batchactionlist %}
          <tr>
          <td>{{ batchacction.hostaddress }}</td>
          <td>{{ batchacction.username }}</td>
          <td>{{ batchacction.actionshell }}</td>
          <td>{{ batchacction.starttime }}</td>
          </tr>
        {% endfor %}
        </tbody>
</table>
    </div>
  </div>

 

最終效果:



 

三、結束

好啦,Django運維繫統之—批量操作bashshell,並記錄操作用於審計的功能做到這裏就結束咯。下次看看再補充一個執行shell腳本的功能吧。

執行shell腳本功能出來了好像上傳文件的功能也差不多出來了哈哈…

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章