一、介紹
通常使用saltstack都是在master的服務器上直接命令操作,這個對于運維人員來說不是什么大事,但是也會有出錯的時候,而一旦出錯,就會有不可挽回的后果。
二、框架
這里使用django框架,通過對salt-api的封裝,傳入命令,執行api,將結果返回到頁面上顯示。注意:為了防止誤操作,我們對傳入的命令進行了檢查,所有被定義的危險命令將不會被執行。(我這里為了簡單,所以定義了可以被執行的命令。),前端使用了jquery+ajax的方式來不刷新頁面就將結果顯示在頁面上的方式。
三、salt-api的安裝
網上教程很多,我這里就不再廢話了。
四、django代碼
1)、整體結構

2)、salt_api.py(這里參照了github上dzhops的代碼)
# -*- coding: utf-8 -*-
import urllib2, urllib, json
import requests
import json
import ssl
ssl._create_default_https_context = ssl._create_unverified_context
class SaltAPI(object):
def __init__(self, url, username, password):
self.__url = url.rstrip('/')
self.__user = username
self.__password = password
self.__token_id = self.saltLogin()
def saltLogin(self):
params = {'eauth': 'pam', 'username': self.__user, 'password': self.__password}
encode = urllib.urlencode(params)
obj = urllib.unquote(encode)
headers = {'X-Auth-Token': ''}
url = self.__url + '/login'
req = urllib2.Request(url, obj, headers)
opener = urllib2.urlopen(req)
content = json.loads(opener.read())
try:
token = content['return'][0]['token']
return token
except KeyError:
raise KeyError
def postRequest(self, obj, prefix='/'):
url = self.__url + prefix
headers = {'X-Auth-Token': self.__token_id}
req = urllib2.Request(url, obj, headers)
opener = urllib2.urlopen(req)
content = json.loads(opener.read())
return content
def masterToMinionContent(self, tgt, fun, arg):
'''
Master控制Minion,返回的結果是內容,不是jid;
目標參數tgt是一個如下格式的字符串:'*' 或 'zhaogb-201'
'''
if tgt == '*':
params = {'client': 'local', 'tgt': tgt, 'fun': fun, 'arg': arg}
else:
params = {'client': 'local', 'tgt': tgt, 'fun': fun, 'arg': arg, 'expr_form': 'list'}
obj = urllib.urlencode(params)
content = self.postRequest(obj)
result = content['return'][0]
return result
def allMinionKeys(self):
'''
返回所有Minion keys;
分別為 已接受、待接受、已拒絕;
:return: [u'local', u'minions_rejected', u'minions_denied', u'minions_pre', u'minions']
'''
params = {'client': 'wheel', 'fun': 'key.list_all'}
obj = urllib.urlencode(params)
content = self.postRequest(obj)
minions = content['return'][0]['data']['return']['minions']
minions_pre = content['return'][0]['data']['return']['minions_pre']
minions_rej = content['return'][0]['data']['return']['minions_rejected']
# return minions, minions_pre, minions_rej
return minions
def actionKyes(self, keystrings, action):
'''
對Minion keys 進行指定處理;
:param keystrings: 將要處理的minion id字符串;
:param action: 將要進行的處理,如接受、拒絕、刪除;
:return:
{"return": [{"tag": "salt/wheel/20160322171740805129", "data": {"jid": "20160322171740805129", "return": {}, "success": true, "_stamp": "2016-03-22T09:17:40.899757", "tag": "salt/wheel/20160322171740805129", "user": "zhaogb", "fun": "wheel.key.delete"}}]}
'''
func = 'key.' + action
params = {'client': 'wheel', 'fun': func, 'match': keystrings}
obj = urllib.urlencode(params)
content = self.postRequest(obj)
ret = content['return'][0]['data']['success']
return ret
def acceptKeys(self, keystrings):
'''
接受Minion發過來的key;
:return:
'''
params = {'client': 'wheel', 'fun': 'key.accept', 'match': keystrings}
obj = urllib.urlencode(params)
content = self.postRequest(obj)
ret = content['return'][0]['data']['success']
return ret
def deleteKeys(self, keystrings):
'''
刪除Minion keys;
:param node_name:
:return:
'''
params = {'client': 'wheel', 'fun': 'key.delete', 'match': keystrings}
obj = urllib.urlencode(params)
content = self.postRequest(obj)
ret = content['return'][0]['data']['success']
return ret3)、views.py
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.shortcuts import render
from django.shortcuts import HttpResponse,HttpResponseRedirect,render_to_response
from models import *
from saltapi import salt_api
from django.http import JsonResponse
import json
def index(request):
accect = []
context = accect_cmd.objects.values()
for i in context:
accect.append(i["command"])
if request.method == "POST":
key = request.POST.get('key')
cmd = request.POST.get('cmd')
if cmd.split( )[0] in accect:
spi = salt_api.SaltAPI('https://ip:8000', 'username', 'password')
result2 = spi.masterToMinionContent(key, 'cmd.run', cmd)
return JsonResponse(result2, safe=False)
else:
data = {key:"請檢查命令是否正確或命令超權限,請聯系管理員!"}
return JsonResponse(data, safe=False)
else:
return render_to_response('index.html')4)、models.py
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models
# Create your models here.
class accect_cmd(models.Model):
command = models.CharField(max_length=50, unique=True, verbose_name=u'命令')
status = models.CharField(max_length=20, verbose_name=u'狀態')
def __unicode__(self):
return u'{0} {1}'.format(self.command, self.status)
class SaltReturns(models.Model):
fun = models.CharField(max_length=50)
jid = models.CharField(max_length=255)
return_field = models.TextField(db_column='return')
success = models.CharField(max_length=10)
full_ret = models.TextField()
alter_time = models.DateTimeField()
class Meta:
managed = False
db_table = 'salt_returns'
def __unicode__(self):
return u'%s %s %s' % (self.jid, self.id, self.return_field)
class record(models.Model):
time = models.DateTimeField(u'時間', auto_now_add=True)
comment = models.CharField(max_length=128, blank=True, default='', null=True, verbose_name=u"記錄")
def __unicode__(self):
return u'%s %s' % (self.time, self.comment)5)、index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>salt平臺</title>
<script src="/static/jquery-2.1.1.min.js"></script>
</head>
<body>
<form action="/salt/index/" method="POST" id="form">
<div>主機:<input type="text" name="key" value="" id="a" ></div>
<div>命令:<input type="text" name="cmd" value="" id="b" ></div>
<div><button type="button" id="fb">執行</button></div>
<div >
<textarea type="text" disabled="disabled" class="left" name="comment" id="c"></textarea>
</div>
</form>
</body>
<script>
$("#fb").click(function () {
$.post("/salt/index/",{
key:$("#a").val(),
cmd: $("#b").val(),
},
function (response,status,xhr) {
$("#c").html('')
$.each(response,function (key,val) {
var c = "\r\n"+key+ ":\r\n" + val;
$("#c").append(c);
})
}
)
})
</script>
</html>五、效果
1)、單個key執行

2)、多個key執行

3)、當命令不被許可時:

六、總結
寫的比較簡陋,而且現在這個版本并不支持類似于192.168.1.1+,192.168.1.*這種正則匹配,后續會繼續增加。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。