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.

283 lines
11 KiB

# Copyright (C) 2005-2024 Splunk Inc. All Rights Reserved.
import json
import socket
import os
import re
import base64
from splunk import BadRequest, ResourceNotFound
from splunk.util import normalizeBoolean
from itsi_py3 import _
from splunk.clilib.bundle_paths import make_splunkhome_path
import splunk.clilib.cli_common as comm
from ITOA.setup_logging import logger as itsi_logger
from . import itsi_module_common as utils
from .itsi_module_interface_object_manifest import object_manifest
class ItsiModuleModules(object):
"""
Class to create, get, handle_action for ITSI module
"""
"""
Class variables
"""
_metadata_base_endpoint_single_module = '/services/apps/local/%s?output_mode=json'
_metadata_base_endpoint_all_modules = '/services/apps/local?search=DA-ITSI&output_mode=json&count=0'
_ALL_MODULES = '-'
_validation_error_messages = {
'package_prefix_mismatch': _('Module package name must start with DA-ITSI-.'),
'label_required': _('Module requires a label.'),
'description_required': _('Module requires a description.'),
'version_required': _('Module requires a version.'),
'author_required': _('Module requires an author.'),
'filename_required': _('No filename specified. Must specify a filename to download.'),
'invalid_filename': _('The filename you are requesting is an invalid SPL package.'),
'no_files_exported': _('Cannot download requested file. No downloads have been exported yet.'),
'package_not_found': _('Requested package does not exist. Either module was not created, or not exported yet.')
}
def __init__(self, session_key, app='SA-ITOA', owner='nobody', logger=None):
"""
Initialize objects
@type session_key: basestring
@param session_key: session key
@type app: basestring
@param app: current app
@type owner: basestring
@param owner: current user
@type logger: object
@param logger: logger to use
@rtype: object
@return: instance of the class
"""
self.session_key = session_key
self.owner = owner
self.app = app
self.logger = logger if logger else itsi_logger
def get(self, itsi_module, **kwargs):
"""
Gets all metadata about a ITSI module
@type itsi_module: string
@param itsi_module: ITSI module that was requested
@rtype: dict
@return: dictionary of ITSI module metadata
"""
# Set up target endpoint to make request to, and then attempt to get data
if itsi_module == self._ALL_MODULES:
target_endpoint = self._metadata_base_endpoint_all_modules
else:
target_endpoint = self._metadata_base_endpoint_single_module % itsi_module
self.logger.debug('Attempting get from modules from endpoint: %s', target_endpoint)
# Construct response that provides all module metadata
response = utils.construct_metadata_response(target_endpoint, itsi_module, self.session_key, **kwargs)
self.logger.debug('Get response for module %s: %s', itsi_module, json.dumps(response))
return response
def handle_action(self, itsi_module, module_action_name, **kwargs):
"""
Handle custom action for ITSI module
@type itsi_module: basestring
@param itsi_module: the name of ITSI module
@type module_action_name: basestring
@param module_action_name: custom action for ITSI module
@type kwargs: dict
@param kwargs: extra params
@rtype: dict if module_action_name is generate_package
@return: - if module_action_name is generate_package,
appid, url path, file path of ITSI module that is packaged.
- if module_action_name is validate, return a list of validation
errors/infos related to all of the object types and metadata
- if module_action_name is download_package, return the data for
the packaged .spl file that should be downloaded. If the file doesn't
exist, return a 404
"""
self._validate_module_name(itsi_module)
self._validate_module_action(module_action_name)
self.logger.debug('Executing action %s', module_action_name)
if module_action_name == 'generate_package':
return self._handle_generate_package_action(itsi_module, **kwargs)
elif module_action_name == 'validate':
return self._handle_validate_module(itsi_module, **kwargs)
elif module_action_name == 'download_package':
return self._handle_download_module(itsi_module, **kwargs)
def list_contents(self, itsi_module, object_instances, **kwargs):
"""
List object contents in ITSI module
@type itsi_module: basestring
@param itsi_module: the name of ITSI module
@type object_instances: list
@param object_instances: objects of supported object types
@type kwargs: dict
@param kwargs: extra params
@rtype: dict
@return:
{
<object_type>: []
}
"""
response = {}
if type(object_instances) is list:
for object_instance in object_instances:
object_content = object_instance.get(itsi_module, None, **kwargs)
for entry in object_content:
object_type = entry['object_type']
if object_type not in response:
response[object_type] = []
response[object_type].append(entry)
return response
def _validate_module_name(self, itsi_module):
"""
Validate passed ITSI module name
"""
if not itsi_module:
msg = _('ITSI module name is not valid.')
self.logger.error(msg)
raise BadRequest(extendedMessages=msg)
def _validate_module_action(self, module_action_name):
"""
Validate passed ITSI module action
"""
msg = ''
if not module_action_name:
msg = _('ITSI module action is not valid.')
elif module_action_name not in ['generate_package', 'validate', 'download_package']:
msg = _('ITSI module action is not supported: {}.').format(module_action_name)
if msg:
self.logger.error(msg)
raise BadRequest(extendedMessages=msg)
def _handle_validate_module(self, itsi_module, **kwargs):
"""
Validate the module contents
@type itsi_module: basestring
@param itsi_module: the name of itsi module
@type kwargs: dict
@param kwargs: extra params
@rtype: dictionary
@return: dictionary of object type to actual validation result
"""
all_results = {
'module': {}
}
module_metadata = self.get(itsi_module)
module_errors = []
if not module_metadata.get('package_name').startswith(itsi_module):
module_errors.append([itsi_module, itsi_module, self._validation_error_messages['package_prefix_mismatch']])
if not module_metadata.get('label'):
module_errors.append([itsi_module, itsi_module, self._validation_error_messages['label_required']])
if not module_metadata.get('description'):
module_errors.append([itsi_module, itsi_module, self._validation_error_messages['description_required']])
if not module_metadata.get('version'):
module_errors.append([itsi_module, itsi_module, self._validation_error_messages['version_required']])
if not module_metadata.get('author'):
module_errors.append([itsi_module, itsi_module, self._validation_error_messages['author_required']])
if len(module_errors):
all_results['module']['errors'] = module_errors
# Validate all the supported objects
for object_type in list(object_manifest.keys()):
if not type(object_manifest[object_type]) is list:
object_instance = object_manifest[object_type](self.session_key)
all_results[object_type] = object_instance.validate(itsi_module, None)
self.logger.debug('Found following validation results for module %s: %s', itsi_module, all_results)
return all_results
def _handle_download_module(self, itsi_module, **kwargs):
"""
Handles the module download given a file name passed into the URL
@type itsi_module: string
@param itsi_module: ITSI module that is being requested
@rtype: dictionary
@return: Dictionary containing a single field "file_contents" with the base64 encoded file contents inside
"""
response_object = {}
file_data = ''
VALID_FILENAME_PATTERN = r'^DA-ITSI-([0-9a-zA-Z_]+)_([0-9]_?)+\.spl$'
# Make sure that the filename is provided
filename = kwargs.get('filename')
if not filename:
raise BadRequest(extendedMessages=self._validation_error_messages.get('filename_required'))
# Make sure that the user is requesting a valid SPL file
valid_filename = re.match(VALID_FILENAME_PATTERN, filename)
if not valid_filename:
raise BadRequest(extendedMessages=self._validation_error_messages.get('invalid_filename'))
download_folder_path = make_splunkhome_path(['etc',
'apps', 'SA-ITOA',
'appserver', 'static', 'download'])
# If path doesn't exist, or is a file, no downloads have been exported yet
if not os.path.isdir(download_folder_path):
raise ResourceNotFound(self._validation_error_messages.get('no_files_exported'))
file_path = os.path.join(download_folder_path, filename)
# If filename doesn't exist, either module not created or may not have been exported yet
if not os.path.isfile(file_path):
raise ResourceNotFound(extendedMessages=self._validation_error_messages.get('package_not_found'))
# Open file, base 64 encode the file contents, and dump into the response object
try:
with open(file_path, 'r') as file:
file_data = file.read()
response_object['file_contents'] = base64.standard_b64encode(file_data)
except Exception:
raise utils.ItsiModuleError(status=500, message=_('Failed to get contents of SPL file.'))
return response_object
def _get_splunk_web_uri():
"""
Returns Splunkweb uri
"""
return comm.getWebUri().replace('127.0.0.1', socket.gethostname().lower())