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.
171 lines
6.6 KiB
171 lines
6.6 KiB
import appsmanager
|
|
import splunk
|
|
import splunk.admin as admin
|
|
import splunk.appbuilder as appbuilder
|
|
import re
|
|
import os
|
|
import json
|
|
import splunk.bundle as bundle
|
|
from splunk.clilib.bundle_paths import make_splunkhome_path
|
|
|
|
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"
|
|
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):
|
|
|
|
if self.requestedAction == admin.ACTION_CREATE:
|
|
# Let the C++ handler do all the validation work.
|
|
self.supportedArgs.addOptArg('*')
|
|
|
|
if self.customAction == ACTION_PACKAGE:
|
|
limits_conf = bundle.getConf('limits', sessionKey=self.getSessionKey())
|
|
enableInstallApps = limits_conf['auth'].get("enable_install_apps", False)
|
|
if (splunk.util.normalizeBoolean(enableInstallApps)):
|
|
self.customActionCap = EDIT_LOCAL_APPS_CAP_EXPR
|
|
return
|
|
|
|
# default
|
|
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
|
|
try:
|
|
url, path = appbuilder.packageApp(appName)
|
|
|
|
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)
|
|
|
|
'''
|
|
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)
|