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
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())
|