Source code for vent.menus.editor
import ast
import copy
import json
import os
import re
import npyscreen
from vent.api.plugin_helpers import PluginHelper
from vent.api.templates import Template
from vent.menus.del_instances import DeleteForm
[docs]class EditorForm(npyscreen.ActionForm):
""" Form that can be used as a pseudo text editor in npyscreen """
def __init__(self, repo='', tool_name='', branch='', version='',
next_tool=None, just_downloaded=False, vent_cfg=False,
from_registry=False, new_instance=False, *args, **keywords):
""" Initialize EditorForm objects """
# default for any editor
self.settings = locals()
self.settings.update(keywords)
del self.settings['self']
del self.settings['args']
del self.settings['keywords']
del self.settings['parentApp']
self.p_helper = PluginHelper(plugins_dir='.internals/')
self.tool_identifier = {'name': tool_name,
'branch': branch,
'version': version}
self.settings.update(self.tool_identifier)
del self.settings['name']
self.settings['tool_name'] = tool_name
self.settings['next_tool'] = next_tool
self.settings['repo'] = repo
# setup checks
self.just_downloaded = ('just_downloaded' in self.settings and
self.settings['just_downloaded'])
self.vent_cfg = ('vent_cfg' in self.settings and
self.settings['vent_cfg'])
self.registry_tool = ('from_registry' in self.settings and
self.settings['from_registry'])
self.instance_cfg = ('new_instance' in self.settings and
self.settings['new_instance'])
# get manifest info for tool that will be used throughout
if not self.just_downloaded and not self.vent_cfg:
result = self.p_helper.constraint_options(self.tool_identifier, [])
tool, self.manifest = result
self.section = list(tool.keys())[0]
# get configuration information depending on type
if self.just_downloaded:
self.config_val = '[info]\n'
self.config_val += 'name = ' + keywords['link_name'] + '\n'
self.config_val += 'groups = ' + keywords['groups'] + '\n'
elif self.vent_cfg:
self.config_val = keywords['get_configure'](main_cfg=True)[1]
self.settings['tool_name'] = 'vent configuration'
elif self.instance_cfg:
path = self.manifest.option(self.section, 'path')[1]
# defaults in .internals
path = path.replace('.vent/plugins',
'.vent/.internals')
multi_tool = self.manifest.option(self.section, 'multi_tool')
if multi_tool[0] and multi_tool[1] == 'yes':
name = self.manifest.option(self.section, 'name')[1]
if name == 'unspecified':
name = 'vent'
template_path = os.path.join(path,
name + '.template')
else:
template_path = os.path.join(path, 'vent.template')
# ensure instances is in the editor and that it is the right number
template = Template(template_path)
template.add_section('settings')
template.set_option('settings', 'instances',
str(self.settings['new_instances']))
template.write_config()
with open(template_path, 'r') as vent_template:
self.config_val = vent_template.read()
else:
self.config_val = keywords['get_configure'](
**self.tool_identifier)[1]
super(EditorForm, self).__init__(*args, **keywords)
[docs] def create(self):
""" Create multi-line widget for editing """
# add various pointers to those editing vent_cfg
if self.vent_cfg:
self.add(npyscreen.Textfield,
value='# when configuring external'
' services make sure to do so',
editable=False)
self.add(npyscreen.Textfield,
value='# in the form of Service = {"setting": "value"}',
editable=False)
self.add(npyscreen.Textfield,
value='# make sure to capitalize your service correctly'
' (i.e. Elasticsearch vs. elasticsearch)',
editable=False)
self.add(npyscreen.Textfield,
value='# and make sure to enclose all dict keys and'
' values in double quotes ("")',
editable=False)
self.add(npyscreen.Textfield,
value='',
editable=False)
elif self.instance_cfg:
self.add(npyscreen.Textfield,
value='# these settings will be used'
' to configure the new instances',
editable=False)
self.edit_space = self.add(npyscreen.MultiLineEdit,
value=self.config_val)
[docs] def change_screens(self):
""" Change to the next tool to edit or back to MAIN form """
if self.settings['next_tool']:
self.parentApp.change_form(self.settings['next_tool'])
else:
self.parentApp.change_form('MAIN')
[docs] @staticmethod
def valid_input(val):
""" Ensure the input the user gave is of a valid format """
# looks for 3 nums followed by a dot 3 times and then ending with
# 3 nums, can be proceeded by any number of spaces
ip_value = re.compile(r'(\d{1,3}\.){3}\d{1,3}$')
# looks for only numbers and commas (because priorities can have commas
# between them), can be proceeded by any number of spaces
all_num = re.compile(r'(\d,?\ *)+$')
sections_comments = re.compile(r"""
\ *\#.* # comments (any number of whitespace, then #
# followed by anything)
| \[[\w-]+\]$ # section headers (any combination of chars, nums,
# underscores, and dashes between brackets)
""", re.VERBOSE)
# can't can be a comment on option side and value side can't have
# [, ], {, or } otherwise it is turned over to literal_eval for
# checkout
options_values = re.compile(r'[^# ]+\ *=[^[\]{}]*$')
line_num = 0
warning_str = ''
error_str = ''
trimmed_val = []
for entry in val.split('\n'):
line_num += 1
# get rid of any extraneous commas at the end of a dict and remove
# extra whitespace from input
trimmed_val.append(re.sub(r',\ *}', '}', entry).strip())
# empty line
if entry.strip() == '':
continue
# look at regular (non dictionary or list) option-value pairs
if options_values.match(entry):
value = entry.split('=', 1)[1]
# deal with potentially more equals signs
for val in value.split('='):
val = val.strip()
# empty val means malformed equals signs
if val == '':
error_str += '-You have a misplaced equals sign on' \
' line ' + str(line_num) + '\n'
# starts with a num; look for bad ip input or warn user
# about having extraneous characters in number input
if re.match('\ *\d', val):
# bad ip syntax
if val.find('.') >= 0 and not ip_value.match(val):
error_str += '-You have an incorrectly' \
' formatted ip address (bad syntax) at' \
' line ' + str(line_num) + '\n'
# possibly malformed numbers
elif val.find('.') < 0 and not all_num.match(val):
warning_str += '-Line starting with a number has' \
' characters mixed in at line ' + \
str(line_num) + '\n'
# bad ip values
elif val.find('.') >= 0:
for num in val.strip().split('.'):
num = int(num)
if num > 255 or num < 0:
error_str += '-You have an incorrectly' \
' formatted ip address (values' \
' exceeding 255 or below 0) at' \
' line ' + str(line_num) + '\n'
# ensure no lines end with a comma (most likely extraneous
# commas from groups or priorities)
if re.search(',$', val):
error_str += '-You have an incorrect comma at the' \
' end of line ' + str(line_num) + '\n'
# see if input is a header or comment, otherwise try to
# literal_eval it to ensure correct structure
elif not sections_comments.match(entry):
lit_val = ''
try:
opt_val = entry.split('=', 1)
if opt_val[0].strip() == '':
error_str += '-You have nothing preceeding an' \
' equals sign at line ' + str(line_num) + '\n'
else:
lit_val = opt_val[1].strip()
except IndexError:
lit_val = ''
error_str += '-You have an incorrectly formatted' \
' section header at line ' + str(line_num) + '\n'
if lit_val:
try:
ast.literal_eval(lit_val)
except SyntaxError:
error_str += '-You have an incorrectly formatted' \
' list/dictionary at line ' + str(line_num) + \
'\n'
if error_str:
npyscreen.notify_confirm('You have the following error(s) and'
" can't proceed until they are fixed:" +
'\n' + '-'*50 + '\n' + error_str,
title='Error in input')
return (False, '')
elif warning_str:
res = npyscreen.notify_yes_no('You have may have some error(s)'
' that you want to check before'
' proceeding:' + '\n' + '-'*50 +
'\n' + warning_str + '\n' + '-'*50 +
'\n' + 'Do you want to continue?',
title='Double check')
return (res, '\n'.join(trimmed_val))
return (True, '\n'.join(trimmed_val))
[docs] def on_ok(self):
""" Save changes made to vent.template """
# ensure user didn't have any syntactical errors
input_is_good, trimmed_input = self.valid_input(self.edit_space.value)
if not input_is_good:
return
self.edit_space.value = trimmed_input
# get the number of instances and ensure user didn't malform that
if re.search(r'instances\ *=', self.edit_space.value):
try:
# split out spaces
instances_val = re.split(r'instances\ *=\ *',
self.edit_space.value)[1]
instances_val = instances_val.split('\n')[0]
new_instances = int(re.match(r'\d+$', instances_val).group())
except AttributeError:
npyscreen.notify_confirm("You didn't specify a valid number"
' for instances.', title='Invalid'
' instance number')
return
# user can't change instances when configuring new instnaces
if (self.instance_cfg and
self.settings['new_instances'] != new_instances):
npyscreen.notify_confirm("You can't change the number of"
' instnaces while configuring new'
' instances!', title='Illegal change')
return
# get old number of instances
try:
if 'old_instances' in self.settings:
old_instances = self.settings['old_instances']
else:
settings_dict = json.loads(
self.manifest.option(self.section,
'settings')[1])
old_instances = int(settings_dict['instances'])
except Exception:
old_instances = 1
else:
new_instances = 1
old_instances = 1
# save changes and update manifest we're looking at with changes
if self.vent_cfg:
save_args = {'main_cfg': True,
'config_val': self.edit_space.value}
self.manifest = self.settings['save_configure'](**save_args)[1]
else:
save_args = copy.deepcopy(self.tool_identifier)
save_args.update({'config_val': self.edit_space.value})
if self.registry_tool:
save_args.update({'from_registry': True})
if self.instance_cfg:
save_args.update({'instances': new_instances})
self.manifest = self.settings['save_configure'](**save_args)[1]
# restart tools, if necessary
if not self.just_downloaded and not self.instance_cfg:
restart_kargs = {'main_cfg': self.vent_cfg,
'old_val': self.config_val,
'new_val': self.edit_space.value}
if self.vent_cfg:
wait_str = 'Restarting tools affected by changes...'
else:
wait_str = 'Restarting this tool with new settings...'
restart_kargs.update(self.tool_identifier)
npyscreen.notify_wait(wait_str,
title='Restarting with changes')
self.settings['restart_tools'](**restart_kargs)
# start new instances if user wanted to
if self.instance_cfg and self.settings['start_new']:
npyscreen.notify_wait('Starting new instances...',
title='Start')
tool_d = {}
for i in range(self.settings['old_instances'] + 1,
self.settings['new_instances'] + 1):
# create section by scrubbing instance number out of names
# and adding new instance number
i_section = self.section.rsplit(':', 2)
i_section[0] = re.sub(r'[0-9]', '', i_section[0]) + str(i)
i_section = ':'.join(i_section)
t_name = self.manifest.option(i_section, 'name')[1]
t_branch = self.manifest.option(i_section, 'branch')[1]
t_version = self.manifest.option(i_section, 'version')[1]
t_id = {'name': t_name,
'branch': t_branch,
'version': t_version}
tool_d.update(self.settings['prep_start'](**t_id)[1])
if tool_d:
self.settings['start_tools'](tool_d)
# prompt user for instance changes, as necessary
if not self.instance_cfg and not self.vent_cfg:
if new_instances > old_instances:
try:
diff = str(new_instances - old_instances)
res = npyscreen.notify_yes_no('You will be creating ' +
diff + ' additional'
' instance(s) is that okay?',
title='Confirm new'
' instance(s)')
if res:
if self.manifest.option(self.section,
'built')[1] == 'yes':
run = npyscreen.notify_yes_no('Do you want to'
' start these new'
' tools upon'
' creation?',
title='Run new'
' instance(s)')
else:
run = False
# get clean name (no instance numbers in it)
new_name = self.settings['tool_name']
new_name = re.sub(r'[0-9]+$', '', new_name)
self.settings['tool_name'] = new_name
npyscreen.notify_wait('Pulling up default settings'
' for ' +
self.settings['tool_name'] +
'...',
title='Gathering settings')
self.p_helper.clone(self.settings['repo'])
self.settings['new_instances'] = new_instances
self.settings['old_instances'] = old_instances
self.settings['start_new'] = run
self.settings['new_instance'] = True
self.settings['name'] = 'Configure new instance(s)' + \
' for ' + self.settings['tool_name']
self.parentApp.addForm('INSTANCEEDITOR' +
self.settings['tool_name'],
EditorForm, **self.settings)
self.parentApp.change_form('INSTANCEEDITOR' +
self.settings['tool_name'])
else:
return
except Exception:
npyscreen.notify_confirm('Trouble finding tools to add,'
' exiting', title='Error')
self.on_cancel()
elif new_instances < old_instances:
try:
diff = str(old_instances - new_instances)
res = npyscreen.notify_yes_no('You will be deleting ' +
diff + ' instance(s), is'
' that okay?',
title='Confirm delete'
' instance(s)')
if res:
form_name = 'Delete instances for ' + \
re.sub(r'\d+$', '',
self.settings['tool_name']) + '\t'*8 + \
'^E to exit configuration process'
clean_section = self.section.rsplit(':', 2)
clean_section[0] = re.sub(r'\d+$', '',
clean_section[0])
clean_section = ':'.join(clean_section)
d_args = {'name': form_name,
'new_instances': new_instances,
'old_instances': old_instances,
'next_tool': self.settings['next_tool'],
'manifest': self.manifest,
'section': clean_section,
'clean': self.settings['clean'],
'prep_start': self.settings['prep_start'],
'start_tools': self.settings['start_tools']}
self.parentApp.addForm('DELETER' +
self.settings['tool_name'],
DeleteForm, **d_args)
self.parentApp.change_form('DELETER' +
self.settings['tool_name'])
except Exception:
npyscreen.notify_confirm('Trouble finding instances to'
' delete, exiting', title='Error')
self.on_cancel()
if (new_instances == old_instances or
self.instance_cfg or self.vent_cfg):
npyscreen.notify_confirm('Done configuring ' +
self.settings['tool_name'],
title='Configurations saved')
self.change_screens()
[docs] def on_cancel(self):
""" Don't save changes made to vent.template """
npyscreen.notify_confirm('No changes made to ' +
self.settings['tool_name'],
title='Configurations not saved')
self.change_screens()