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.

223 lines
9.4 KiB

8 months ago
import appsmanager
import splunk
import splunk.admin as admin
import splunk.appbuilder as appbuilder
import re
import os
import json
from splunk.clilib.bundle_paths import make_splunkhome_path
import splunk.clilib.cli_common as cli_common
import splunk.spl2helper as spl2helper
HTTP_POST_TEMPLATE = "template"
HTTP_POST_LABEL = "label"
HTTP_POST_DESC = "description"
HTTP_POST_VISIBLE = "visible"
HTTP_POST_AUTHOR = "author"
HTTP_POST_CONFIGURED = "configured"
HTTP_POST_VERSION = "version"
ACTION_PACKAGE = "package"
ACTION_DEPENDENCIES = "dependencies"
ACTION_SYNC_OBJECTS = "syncfs"
EDIT_LOCAL_APPS_CAP_EXPR = "edit_local_apps"
AAO_OR_EDIT_LOCAL_APPS_CAP_EXPR = "admin_all_objects or edit_local_apps"
DEFAULT_TEMPLATE = "barebones"
class LocalAppsHandler(admin.MConfigHandler):
def __init__(self, scriptMode, ctxInfo):
admin.MConfigHandler.__init__(self, scriptMode, ctxInfo)
# This handler already lists upon create/edit, turn this off.
self.shouldAutoList = False
'''
Set up supported arguments
'''
def setup(self):
enableInstallApps = cli_common.getConfKeyValue("limits", "auth", "enable_install_apps")
if self.requestedAction == admin.ACTION_CREATE:
# Let the C++ handler do all the validation work.
self.supportedArgs.addOptArg('*')
# SPL-207748: implementing auth for localapps-python endpoint
if (splunk.util.normalizeBoolean(enableInstallApps)):
self.setWriteCapability(EDIT_LOCAL_APPS_CAP_EXPR)
else:
# default
self.setWriteCapability(AAO_OR_EDIT_LOCAL_APPS_CAP_EXPR)
if self.customAction == ACTION_PACKAGE:
# SPL-158999: to support merge-local-meta and exclude-local-meta parameters
self.supportedArgs.addOptArg('merge-local-meta')
self.supportedArgs.addOptArg('exclude-local-meta')
if (splunk.util.normalizeBoolean(enableInstallApps)):
self.customActionCap = EDIT_LOCAL_APPS_CAP_EXPR
return
# default
self.customActionCap = AAO_OR_EDIT_LOCAL_APPS_CAP_EXPR
if self.customAction == ACTION_SYNC_OBJECTS:
self.supportedArgs.addReqArg('object_types')
self.supportedArgs.addOptArg('dest')
self.supportedArgs.addOptArg('filter')
self.customActionCap = AAO_OR_EDIT_LOCAL_APPS_CAP_EXPR
'''
Create a new application
'''
def handleCreate(self, confInfo):
args = self.callerArgs.data
# Sanity checking for app ID: no special chars and shorter than 100 chars
appName = self.callerArgs.id
if not appName or len(appName) == 0:
raise admin.ArgValidationException('App folder name is not set.')
if re.search('[^A-Za-z0-9._-]', appName):
raise admin.ArgValidationException('App folder name cannot contain spaces or special characters.')
if len(appName) > 100:
raise admin.ArgValidationException('App folder name cannot be longer than 100 characters.')
kwargs = {
'label' : _getFieldValue(args, HTTP_POST_LABEL, appName, maxLen=100),
'visible' : _getFieldValue(args, HTTP_POST_VISIBLE, 'true'),
'author' : _getFieldValue(args, HTTP_POST_AUTHOR, '', maxLen=100),
'description' : _getFieldValue(args, HTTP_POST_DESC, '', maxLen=500),
'configured' : _getFieldValue(args, HTTP_POST_CONFIGURED, '0'),
'version' : _getFieldValue(args, HTTP_POST_VERSION, '1.0.0')
}
template = _getFieldValue(args, HTTP_POST_TEMPLATE, DEFAULT_TEMPLATE)
if re.match("^\d{1,3}\.\d{1,3}\.\d{1,3}(\s?\w[\w\d]{,9})?$", kwargs['version']) is None:
raise admin.ArgValidationException("Version '%s' is invalid. Use the version format 'major.minor.patch', for example '1.0.0'." % kwargs['version'])
try:
url = appbuilder.createApp(appName, template, **kwargs)
appbuilder.addUploadAssets(appName)
except splunk.RESTException as e:
raise admin.InternalException(e.msg)
confInfo[appName].append('name', appName)
for field in kwargs:
confInfo[appName].append(field, kwargs[field])
'''
Controls local applications
'''
def handleEdit(self, confInfo):
appName = self.callerArgs.id
appbuilder.addUploadAssets(appName)
'''
Handles other commands
'''
def handleCustom(self, confInfo):
action = self.customAction
actionType = self.requestedAction
if self.customAction == ACTION_PACKAGE:
if appsmanager.isCloud(self.getSessionKey()):
raise admin.BadActionException("'package' action is not supported for Splunk Cloud.")
# Create a package of an application
confInfo.addWarnMsg('The package action has been deprecated.')
appName = self.callerArgs.id
# obtain merge-local-meta and exclude-local-meta parameters
# if the respective arguments are not passed, we want to package local.meta and default.meta as is
mergeLocalMeta = self.callerArgs.get('merge-local-meta', ['f'])[0]
excludeLocalMeta = self.callerArgs.get('exclude-local-meta', ['f'])[0]
try:
mergeLocalMeta = splunk.util.normalizeBoolean(mergeLocalMeta, enableStrictMode=True)
excludeLocalMeta = splunk.util.normalizeBoolean(excludeLocalMeta, enableStrictMode=True)
except ValueError as e:
raise admin.ArgValidationException("Incorrect parameter value. Please specify one of the following: "
"true/false, t/f, or 0/1.")
if (mergeLocalMeta and excludeLocalMeta):
raise admin.ArgValidationException("Invalid parameter specification. Both -merge-local-meta and "
"-exclude-local-meta cannot be \"true\" at the same time.")
try:
url, path = appbuilder.packageApp(appName, excludeLocalMeta=excludeLocalMeta, mergeLocalMeta=mergeLocalMeta)
confInfo['Package'].append('name', appName)
confInfo['Package'].append('url', url)
confInfo['Package'].append('path', path)
except splunk.RESTException as e:
raise admin.ArgValidationException(e.msg)
elif self.customAction == ACTION_DEPENDENCIES:
self.getAppDependencies(self.callerArgs.id, confInfo)
elif self.customAction == ACTION_SYNC_OBJECTS:
"""
Sync objects to the app's local file-system which are maintained externally (eg: kv-store etc)
"""
app_name = self.callerArgs.id
object_types_arg = self.callerArgs.get('object_types', [""])[0]
confInfo['SyncFs'].append('name', app_name)
app_location = make_splunkhome_path(['etc', 'apps', app_name])
if not os.path.isdir(app_location):
raise splunk.ResourceNotFound('App %s does not exist.' % app_name)
try:
for ob_type in list(object_types_arg.split(',')):
ob_type = ob_type.lower().strip()
if ob_type.startswith("spl2"):
spl2helper.sync_spl2_objects(self.callerArgs, ob_type, self.getSessionKey(), app_name)
except splunk.RESTException as e:
raise admin.InternalException(e.msg)
'''
This handler is overridden by UIAppsHandler
'''
def handleList(self, confInfo):
pass
'''
Get application dependencies from its manifest file
'''
def getAppDependencies(self, app, confInfo):
app_location = make_splunkhome_path(['etc', 'apps', app])
if not os.path.isdir(app_location):
raise splunk.ResourceNotFound('App %s does not exist.' % app)
manifestFile = os.path.join(app_location, 'app.manifest')
if not os.path.exists(str(manifestFile)):
# not an error, app manifest is optional
confInfo.addInfoMsg('Application manifest file is missing.')
return
# read app manifest chopping possible comments (that start with '#')
manifest = ''.join([line.split('#')[0] for line in open(manifestFile, 'r').readlines()])
# parse manifest, possible errors will bubble to REST reply
manifest = json.loads(manifest)
dependencies = manifest.get('dependencies')
if dependencies is None:
confInfo.addInfoMsg("Application manifest doesn't include dependencies.")
return
# construct reply
reply = confInfo[app]
for dependency, properties in dependencies.items():
# Convert properties to STR as it returns unicode
reply[dependency] = str(properties.get("version"))
def _getFieldValue(args, fieldName, defaultVal=None, maxLen=None):
value = args[fieldName][0] or defaultVal if fieldName in args else defaultVal
if value and maxLen and len(value) > maxLen:
raise admin.ArgValidationException('App %(fieldName)s cannot be longer than %(maxLen)s characters.'
% {'fieldName' : fieldName, 'maxLen' : maxLen} )
return value
# initialize the handler, and add the actions it supports.
admin.init(LocalAppsHandler, admin.CONTEXT_NONE)

Powered by BW's shoe-string budget.