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.
249 lines
9.7 KiB
249 lines
9.7 KiB
import json
|
|
import os
|
|
import sys
|
|
import http.client
|
|
|
|
import splunk.rest as rest
|
|
from splunk.persistconn.application import PersistentServerConnectionApplication
|
|
from splunk.clilib.bundle_paths import make_splunkhome_path
|
|
|
|
sys.path.append(make_splunkhome_path(['etc', 'apps', 'DA-ITSI-ContentLibrary', 'lib']))
|
|
|
|
from itsi_content_utils import HTTPError, SplunkMessageHandler
|
|
from itsi_content_setup_logging import logger
|
|
from itsi_content_constants import CONTENT_PACK_PREFIX
|
|
|
|
|
|
class ContentPackDiscoveryHandler(PersistentServerConnectionApplication):
|
|
def __init__(self, command_line, command_arg):
|
|
"""
|
|
Basic constructor
|
|
|
|
@type: string
|
|
@param command_line: command line invoked for handler
|
|
|
|
@type: string
|
|
@param command_arg: args for invoked command line for handler
|
|
"""
|
|
super(PersistentServerConnectionApplication, self).__init__()
|
|
self.content_pack_conf_path = \
|
|
rest.makeSplunkdUri() + 'servicesNS/nobody/DA-ITSI-ContentLibrary/configs/conf-itsi_content_packs'
|
|
|
|
def handle(self, args):
|
|
"""
|
|
Blanket handler for all REST calls on the interface routing the GET/POST/PUT/DELETE requests.
|
|
Derived implementation from PersistentServerConnectionApplication.
|
|
|
|
@type args: json
|
|
@param args: a JSON string representing a dictionary of arguments to the REST call.
|
|
|
|
@rtype: json
|
|
@return: a valid REST response
|
|
"""
|
|
try:
|
|
args = json.loads(args)
|
|
rest_method = args['method']
|
|
session_key = args['session']['authtoken']
|
|
if rest_method != 'POST':
|
|
raise HTTPError(
|
|
status=http.client.INTERNAL_SERVER_ERROR,
|
|
message="Unsupported HTTP method {}.".format(rest_method)
|
|
)
|
|
apps = self.get_app_ids(session_key)
|
|
valid_apps = apps['valid_apps']
|
|
added_apps, failed_added_apps = self.add_apps(apps['apps_to_be_added'], session_key)
|
|
removed_apps, failed_removed_apps = self.remove_apps(apps['apps_to_be_removed'], valid_apps, session_key)
|
|
return {
|
|
'payload': {
|
|
'success': {
|
|
'apps_added': added_apps,
|
|
'apps_removed': removed_apps
|
|
},
|
|
'failed': {
|
|
'apps_added': failed_added_apps,
|
|
'apps_removed': failed_removed_apps
|
|
},
|
|
'current_apps_list': valid_apps
|
|
},
|
|
'status': http.client.OK
|
|
}
|
|
except Exception as e:
|
|
logger.exception(e)
|
|
return {
|
|
'payload': {
|
|
'exception': 'failed to reload content apps because {}'.format(e)
|
|
},
|
|
'status': http.client.INTERNAL_SERVER_ERROR
|
|
}
|
|
|
|
def add_apps(self, apps_to_be_added, session_key):
|
|
"""
|
|
Add apps which exist on the file system
|
|
but are not registered as stanzas to itsi_content_packs.conf
|
|
|
|
@type: list
|
|
@param apps_to_be_added: a list of app names that need to be registered
|
|
|
|
@type: string
|
|
@param session_key: the splunkd session key for the request
|
|
|
|
@rtype: list
|
|
@return: a list of successfully added app names
|
|
"""
|
|
added_apps = []
|
|
failed_added_apps = []
|
|
for app_name in apps_to_be_added:
|
|
file_path = make_splunkhome_path([
|
|
'etc',
|
|
'apps',
|
|
app_name,
|
|
'itsi',
|
|
'config.json'
|
|
])
|
|
stanza_args = {}
|
|
with open(file_path, 'r') as f:
|
|
config = json.load(f)
|
|
stanza_args['name'] = config.get('id')
|
|
stanza_args['description'] = config.get('description')
|
|
stanza_args['title'] = config.get('title')
|
|
stanza_args['version'] = config.get('version')
|
|
stanza_args['isCustom'] = 1
|
|
try:
|
|
response, content = rest.simpleRequest(
|
|
self.content_pack_conf_path,
|
|
method='POST',
|
|
postargs=stanza_args,
|
|
sessionKey=session_key,
|
|
raiseAllErrors=False
|
|
)
|
|
if int(response['status']) == http.client.OK or int(response['status']) == http.client.CREATED:
|
|
added_apps.append(app_name)
|
|
logger.info('Added stanza {} to itsi_content_packs.conf'.format(app_name))
|
|
else:
|
|
failed_added_apps.append({app_name: {'response': str(response), 'content': str(content)}})
|
|
except Exception as e:
|
|
failed_added_apps.append({app_name: e.message})
|
|
message_handler = SplunkMessageHandler(session_key)
|
|
message = 'Failed to create the content pack stanza {}. Reason: {}'.format(app_name, e)
|
|
logger.error(message)
|
|
message_handler.post_or_update_message(app_name,
|
|
SplunkMessageHandler.ERROR,
|
|
message)
|
|
return added_apps, failed_added_apps
|
|
|
|
def remove_apps(self, apps_to_be_removed, valid_apps, session_key):
|
|
"""
|
|
Remove apps which does not exist on the file system
|
|
but are registered as stanzas to itsi_content_packs.conf
|
|
|
|
@type: list
|
|
@param apps_to_be_removed: a list of app names that need to be removed
|
|
|
|
@type: list
|
|
@param valid_apps: a list of app names that need to be removed from valid apps
|
|
|
|
@type: string
|
|
@param session_key: the splunkd session key for the request
|
|
|
|
@rtype: list
|
|
@return: a list of successfully removed app names
|
|
"""
|
|
removed_apps = []
|
|
failed_removed_apps = []
|
|
for app_name in apps_to_be_removed:
|
|
try:
|
|
response, content = rest.simpleRequest(
|
|
self.content_pack_conf_path + '/' + app_name,
|
|
method='DELETE',
|
|
sessionKey=session_key,
|
|
raiseAllErrors=False
|
|
)
|
|
if int(response['status']) == http.client.OK or int(response['status']) == http.client.CREATED:
|
|
removed_apps.append(app_name)
|
|
valid_apps.remove(app_name)
|
|
logger.info('Removed stanza {} from itsi_content_packs.conf'.format(app_name))
|
|
else:
|
|
failed_removed_apps.append({app_name: {'response': str(response), 'content': str(content)}})
|
|
except Exception as e:
|
|
failed_removed_apps.append({app_name: e.message})
|
|
message_handler = SplunkMessageHandler(session_key)
|
|
message = 'Failed to remove the content pack stanza {}. Reason: {}'.format(app_name, e)
|
|
logger.error(message)
|
|
message_handler.post_or_update_message(app_name,
|
|
SplunkMessageHandler.ERROR,
|
|
message)
|
|
return removed_apps, failed_removed_apps
|
|
|
|
def get_app_ids(self, session_key):
|
|
"""
|
|
Compare the registered apps and actual apps on the file system
|
|
and get a list of apps_to_be_added, apps_to_be_removed and valid_apps
|
|
|
|
@type: string
|
|
@param session_key: the splunkd session key for the request
|
|
|
|
@rtype: dict
|
|
@return: a dict with apps_to_be_added, apps_to_be_removed, valid_apps list
|
|
"""
|
|
filesystem_apps = self.get_content_pack_apps_on_filesystem()
|
|
registered_apps = self.get_content_pack_apps_registered(session_key)
|
|
logger.info('apps on the file systems are: {}'.format(filesystem_apps))
|
|
logger.info('apps registered in the conf file are: {}'.format(registered_apps))
|
|
valid_apps = list(filesystem_apps.union(registered_apps))
|
|
apps_to_be_added = []
|
|
apps_to_be_removed = []
|
|
for app in filesystem_apps:
|
|
if app not in registered_apps:
|
|
apps_to_be_added.append(app)
|
|
for app in registered_apps:
|
|
if app not in filesystem_apps:
|
|
apps_to_be_removed.append(app)
|
|
return {
|
|
'apps_to_be_added': apps_to_be_added,
|
|
'apps_to_be_removed': apps_to_be_removed,
|
|
'valid_apps': valid_apps
|
|
}
|
|
|
|
def get_content_pack_apps_on_filesystem(self):
|
|
"""
|
|
Get registered content pack apps that are on the file system
|
|
All content pack apps start with DA-ITSI-CP-
|
|
|
|
@rtype: set
|
|
@return: set of app names
|
|
"""
|
|
file_path = make_splunkhome_path([
|
|
'etc',
|
|
'apps',
|
|
])
|
|
apps = set()
|
|
for it in os.scandir(file_path):
|
|
if it.is_dir() and it.name.startswith(CONTENT_PACK_PREFIX):
|
|
apps.add(it.name)
|
|
return apps
|
|
|
|
def get_content_pack_apps_registered(self, session_key):
|
|
"""
|
|
Get registered apps in the conf file with enterprise endpoints
|
|
|
|
@type: string
|
|
@param session_key: the splunkd session key for the request
|
|
|
|
@rtype: set
|
|
@return: set of app names
|
|
"""
|
|
|
|
args = {'output_mode': 'json'}
|
|
response, content = rest.simpleRequest(
|
|
self.content_pack_conf_path,
|
|
method='GET',
|
|
getargs=args,
|
|
sessionKey=session_key,
|
|
raiseAllErrors=False
|
|
)
|
|
results = json.loads(content.decode("utf-8")).get('entry')
|
|
apps = set()
|
|
for item in results:
|
|
apps.add(item.get('name'))
|
|
return apps
|