You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

316 lines
11 KiB

from __future__ import absolute_import
import os
import sys
import json
import csv
import cherrypy
import shutil
import splunk
import splunk.auth as auth
import splunk.appserver.mrsparkle.controllers as controllers
import splunk.appserver.mrsparkle.lib.util as util
import splunk.bundle as bundle
from splunk.models.saved_search import SavedSearch
import splunk.util
import splunk.saved
import splunk.search
from splunk.appserver.mrsparkle.lib.decorators import expose_page
from splunk.appserver.mrsparkle.lib.routes import route
from splunk.appserver.mrsparkle.lib import jsonresponse
import six
dir = os.path.join(util.get_apps_dir(), __file__.split('.')[-2], 'bin')
if not dir in sys.path:
sys.path.append(dir)
from sc_rest import setup_logging
logger = setup_logging('unixsetup')
from unix.models.app import App
from unix.models.macro import Macro
from unix.models.unix_setup import UnixConfigured
APP_NAME = 'DA-ITSI-CP-unix-dashboards'
## the macros to be displayed by the setup page
MACROS = [
'os_index',
'cpu_sourcetype',
'df_sourcetype',
'hardware_sourcetype',
'interfaces_sourcetype',
'iostat_sourcetype',
'lastlog_sourcetype',
'lsof_sourcetype',
'memory_sourcetype',
'netstat_sourcetype',
'open_ports_sourcetype',
'package_sourcetype',
'protocol_sourcetype',
'ps_sourcetype',
'rlog_sourcetype',
'syslog_sourcetype',
'time_sourcetype',
'top_sourcetype',
'users_with_login_privs_sourcetype',
'who_sourcetype'
]
'''Unix Setup Controller'''
class UnixSetup(controllers.BaseController):
def parse_dynamic(self, tokenized_string):
try:
seq = tokenized_string.split(' OR ')
# convert to set to deduplicate
set_seq = set(seq)
# convert back to list to sort
tokenlist = list(set_seq)
tokenlist.sort()
except Exception as ex:
tokenlist = []
return tokenlist
def render_json(self, response_data, set_mime="text/json"):
cherrypy.response.headers["Content-Type"] = set_mime
if isinstance(response_data, jsonresponse.JsonResponse):
response = response_data.toJson().replace("</", "<\\/")
else:
response = json.dumps(response_data).replace("</", "<\\/")
return " " * 256 + "\n" + response
@route('/:app/:action=show')
@expose_page(must_login=True, methods=['GET'])
def show(self, app, action, **kwargs):
form_content = {}
user = cherrypy.session['user']['name']
for key in MACROS:
try:
form_content[key] = self.parse_dynamic(Macro.get(Macro.build_id(key, app, user)).definition)
except:
form_content[key] = self.parse_dynamic(Macro(app, user, key).definition)
return self.render_json(form_content)
@route('/:app/:action=save')
@expose_page(must_login=True, methods=['POST'])
def save(self, app, action, **params):
''' save the posted unix setup content '''
error_key = None
form_content = {}
user = cherrypy.session['user']['name']
this_app = App.get(App.build_id(app, app, user))
# pass 1: load all user-supplied values as models
for key, value in six.iteritems(params):
if key and key in MACROS:
definition = (' OR ').join(value.split(","))
try:
form_content[key] = Macro.get(Macro.build_id(key, app, user))
logger.info("Unixsave in try")
except:
form_content[key] = Macro(app, user, key)
logger.info("Unixsave in except")
form_content[key].definition = definition
form_content[key].metadata.sharing = 'app'
# pass 2: try to save(), and if we fail we return the user-supplied values
for key in form_content.keys():
try:
if not form_content[key].passive_save():
logger.error('Error saving setup values')
cherrypy.response.status = 500
except splunk.AuthorizationFailed:
cherrypy.response.status = 403
except Exception as ex:
logger.exception(ex)
logger.error('Failed to save eventtype %s' % key)
cherrypy.response.status = 500
# save build version to unix_setup.conf
unixConfigured = UnixConfigured.get(UnixConfigured.build_id('install', app, 'nobody'))
# Because UnixConfigured resource is 'configs/conf-unix_setup', no way to define required and optional fields there.
# So call SplunkRESTManager._put_args directly, instead of unixConfigured.save(), to bypass required and optional fields check.
newModel = UnixConfigured.manager()._put_args(UnixConfigured.build_id('install', app, 'nobody'), {'configured_version': this_app.version})
logger.info('Save configured version from %s to %s successful' % (unixConfigured.configured_version, this_app.version))
cherrypy.response.status = 200
def insertDictItem(self, k, currentLevel):
if k not in currentLevel:
newLevel = {}
currentLevel[k] = newLevel
return currentLevel[k]
def insertListItem(self, item, k, currentLevel):
if k not in currentLevel:
currentLevel[k] = []
currentLevel[k].append(item)
return currentLevel[k]
def build_tree(self, raw, tree, order):
for row in raw:
# every row starts by inserting from the top-level of the tree
currentLevel = tree
for i, m in enumerate(order):
item = row[m].strip()
# Our leaves are represented by a list of hosts
# these get appended to the hosts key of the last level
if(i == len(order)-1):
self.insertListItem(item, 'hosts', currentLevel)
else:
currentLevel = self.insertDictItem(item, currentLevel)
def tree_to_csv(self, flat, node, csvOrder, hierarchy=[], slot=0):
if(isinstance(node, six.string_types)):
mappedSlot = csvOrder[slot]
hierarchy[mappedSlot] = node
# this makes a copy of hierarchy
flat.append(list(hierarchy))
elif(isinstance(node, dict)):
if('hosts' in node and len(node) == 1):
self.tree_to_csv(flat, node.get('hosts'), csvOrder, hierarchy, slot)
else:
for k, item in six.iteritems(node):
mappedSlot = csvOrder[slot]
hierarchy[mappedSlot] = k
self.tree_to_csv(flat, item, csvOrder, hierarchy, slot+1)
else:
for item in node:
self.tree_to_csv(flat, item, csvOrder, hierarchy, slot)
def getOrder(self, keys, csvHeaders):
order = []
for key in keys:
for i, header in enumerate(csvHeaders):
if key == header.strip():
order.append(i)
return order
@route('/:app/:action=get_categories')
@expose_page(must_login=True, methods=['GET'])
def load_categories(self, app, action, **params):
csvData = []
lookupCSV = os.path.join(util.get_apps_dir(), APP_NAME, 'lookups', 'dropdowns.csv')
# Opening in different modes to handle incorrect parsing issue on windows
if six.PY3:
csvfile = open(lookupCSV, 'rt', newline='')
else:
csvfile = open(lookupCSV, 'rb')
reader = csv.reader(csvfile)
for row in reader:
csvData.append(row)
csvfile.close()
csvHeaders = csvData[0] # this must contain all the column names
keyOrder = ['unix_category', 'unix_group', 'host']
order = self.getOrder(keyOrder, csvHeaders)
csvData = csvData[1:len(csvData)]
tree = {}
self.build_tree(csvData, tree, order)
return self.render_json(tree)
@route('/:app/:action=get_hosts')
@expose_page(must_login=True, methods=['GET'])
def get_hosts(self, app, action, **params):
saved_search = SavedSearch('', cherrypy.session['user']['name'], 'newsearch')
job = splunk.search.dispatch('| metadata type=hosts `metadata_index`', namespace=APP_NAME)
splunk.search.waitForJob(job)
hostData = []
for item in job.results:
hostData.append(six.text_type(item['host']))
return self.render_json(hostData)
@route('/:app/:action=save_categories')
@expose_page(must_login=True, methods=['POST'])
def save_categories(self, app, action, **params):
user = cherrypy.session['user']['name']
for param in params:
data = json.loads(param)
csvData = []
csvOrder = [1,2,0]
self.tree_to_csv(csvData, data, csvOrder, [0,0,0])
csvHeader = ["host", "unix_category", "unix_group"]
csvData.insert(0, csvHeader)
dropdownsCsv = os.path.join(util.get_apps_dir(), APP_NAME, 'lookups', 'dropdowns.csv')
# Opening in different modes to handle incorrect parsing issue on windows
if six.PY3:
csvfile = open(dropdownsCsv, 'wt', newline='')
else:
csvfile = open(dropdownsCsv, 'wb')
writer = csv.writer(csvfile)
writer.writerows(csvData)
csvfile.close()
session_key = cherrypy.session.get('sessionKey')
success, errcode, reason = self._force_lookup_replication(APP_NAME, 'dropdowns.csv', session_key)
logger.info('force lookup replication: %s, %s, %s' % (success, errcode, reason))
def _force_lookup_replication(self, app, filename, sessionKey, base_uri=None):
'''Force replication of a lookup table in a Search Head Cluster.'''
# Permit override of base URI in order to target a remote server.
endpoint = '/services/replication/configuration/lookup-update-notify'
if base_uri:
repl_uri = base_uri + endpoint
else:
repl_uri = endpoint
payload = {'app': app, 'filename': os.path.basename(filename), 'user': 'nobody'}
response, content = splunk.rest.simpleRequest(
repl_uri,
method='POST',
postargs=payload,
sessionKey=sessionKey,
raiseAllErrors=False)
content = content.decode('utf-8')
if response.status == 400:
if 'No local ConfRepo registered' in content:
# search head clustering not enabled
return (True, response.status, content)
elif 'Could not find lookup_table_file' in content:
return (False, response.status, content)
else:
# Previously unforeseen 400 error.
return (False, response.status, content)
elif response.status != 200:
return (False, response.status, content)
return (True, response.status, content)