Commit bb4fd777 authored by Nigel Kukard's avatar Nigel Kukard
Browse files

Cleaned up output and added --textmode option

parent 89bf5dba
...@@ -31,6 +31,8 @@ def main(): ...@@ -31,6 +31,8 @@ def main():
installer_group = argparser.add_argument_group('Installer options') installer_group = argparser.add_argument_group('Installer options')
installer_group.add_argument('--preseed-uri', dest='preseed_uri', action='store', installer_group.add_argument('--preseed-uri', dest='preseed_uri', action='store',
help='Load installer preseed config from URI') help='Load installer preseed config from URI')
installer_group.add_argument('--textmode', dest='textmode', action='store_true',
help='Run installer in text mode')
# Create argument group for optionals # Create argument group for optionals
optional_group = argparser.add_argument_group('Optional arguments') optional_group = argparser.add_argument_group('Optional arguments')
optional_group.add_argument('-h', '--help', action="help", help="Show this help message and exit") optional_group.add_argument('-h', '--help', action="help", help="Show this help message and exit")
...@@ -43,6 +45,8 @@ def main(): ...@@ -43,6 +45,8 @@ def main():
if args.preseed_uri: if args.preseed_uri:
kwargs['preseed_uri'] = args.preseed_uri kwargs['preseed_uri'] = args.preseed_uri
if args.textmode:
kwargs['textmode'] = args.textmode
# Fire up the installer # Fire up the installer
installer = Ili() installer = Ili()
......
...@@ -38,28 +38,38 @@ class Ili: ...@@ -38,28 +38,38 @@ class Ili:
""" """
# The main dialog handle # The main dialog handle
dialog: Dialog _dialog: Dialog
# Running in text mode
_textmode: bool
# Percent done
_percent_done: int
# This is our preseed config if we have one # This is our preseed config if we have one
preseed: ConfigParser _preseed: ConfigParser
# This is the state of the installation
state: IliState
# Plugins # Plugins
plugins: PluginCollection _plugins: PluginCollection
# Block devices on the system # Block devices on the system
block_devices: BlockDevices _block_devices: BlockDevices
# This is the state of the installation
state: IliState
def __init__(self): def __init__(self):
"""Initialize the object.""" """Initialize the object."""
# Load plugins # Fire up Dialog
self.plugins = PluginCollection('idmslinux_installer.plugins') self._dialog = Dialog(dialog='dialog', autowidgetsize=True)
self._dialog.set_background_title(f'IDMS Linux Installer v{__version__}')
self._textmode = True
self._percent_done = 0
# Initialize the config parser to None # Initialize the config parser to None
self.preseed = None self._preseed = None
# Load plugins
self._plugins = PluginCollection('idmslinux_installer.plugins')
# Initialize the install state # Initialize the install state
self.state = IliState() self.state = IliState()
...@@ -67,24 +77,23 @@ class Ili: ...@@ -67,24 +77,23 @@ class Ili:
# Blank the locale # Blank the locale
locale.setlocale(locale.LC_ALL, '') locale.setlocale(locale.LC_ALL, '')
# Fire up Dialog
self.dialog = Dialog(dialog='dialog', autowidgetsize=True)
self.dialog.set_background_title(f'IDMS Linux Installer v{__version__}')
# Set output callback # Set output callback
self.state.output_callback = self._status_callback self.state.output_callback = self._status_callback
def start(self, preseed_uri: Optional[str] = None): def start(self, preseed_uri: Optional[str] = None, **kwargs):
"""Start the installer.""" """Start the installer."""
# Load preseed URI if it was provided # Load preseed URI if it was provided
if preseed_uri: if preseed_uri:
self._status_callback('Downloading preseed "{preseed_uri}"...') self._status_callback(f'Downloading preseed "{preseed_uri}"...')
self._load_preseed(preseed_uri) self._load_preseed(preseed_uri)
else:
# Check if we're operating in text mode
self._textmode = kwargs.get('textmode', False)
if not self.state.install_disks: if not self.state.install_disks:
# Load the system block devices # Load the system block devices
self.block_devices = BlockDevices() self._block_devices = BlockDevices()
self._get_install_disks() self._get_install_disks()
if not self.state.diskusage_strategy: if not self.state.diskusage_strategy:
...@@ -111,19 +120,31 @@ class Ili: ...@@ -111,19 +120,31 @@ class Ili:
if self.state.user_username: if self.state.user_username:
self._get_user_password() self._get_user_password()
# self.dialog.gauge_start("Preparing", title="Installing...", width=75, height=7) if not self._textmode:
self._dialog.gauge_start("Preparing...", title="Installing...", width=75, height=7)
# We always call these
self.plugins.commit_diskusage_strategy(self.state) # Loop with install steps
self.plugins.commit_disklayout_strategy(self.state) steps = [
self.plugins.mount_filesystems(self.state) self._plugins.commit_diskusage_strategy,
self.plugins.pre_install_base(self.state) self._plugins.commit_disklayout_strategy,
self.plugins.install_base(self.state) self._plugins.mount_filesystems,
self.plugins.config_system(self.state) self._plugins.pre_install_base,
self.plugins.install_packages(self.state) self._plugins.install_base,
self.plugins.post_install(self.state) self._plugins.config_system,
self._plugins.install_packages,
# self.dialog.gauge_stop() self._plugins.post_install,
]
for step in steps:
step(self.state)
# Update percentage complete
self._percent_done = int((float(steps.index(step)+1)/float(len(steps)))*100)
self._percent_done = 100
self._status_callback("Install Done")
# End off
if not self._textmode:
self._dialog.gauge_stop()
# C901 - Ignore warning about code complexity # C901 - Ignore warning about code complexity
# R0912 - Ignore warning that we have about the number of branches # R0912 - Ignore warning that we have about the number of branches
...@@ -133,7 +154,7 @@ class Ili: ...@@ -133,7 +154,7 @@ class Ili:
"""Load installer configuration from preseed file.""" """Load installer configuration from preseed file."""
# Parse config # Parse config
self.preseed = ConfigParser() self._preseed = ConfigParser()
# HTTP # HTTP
if re.match('^https?://', preseed_uri): if re.match('^https?://', preseed_uri):
...@@ -157,81 +178,80 @@ class Ili: ...@@ -157,81 +178,80 @@ class Ili:
self._critical_error('Error with preseed URI', f'Preseed URI type is not supported:\n{preseed_uri}') self._critical_error('Error with preseed URI', f'Preseed URI type is not supported:\n{preseed_uri}')
# Read config as string # Read config as string
self.preseed.read_string(config_data) self._preseed.read_string(config_data)
sects = self.preseed.sections() sects = self._preseed.sections()
print(f'SECTIONS: {sects}')
# Process storage section # Process storage section
if self.preseed.has_section('storage'): if self._preseed.has_section('storage'):
# Process install disks # Process install disks
if self.preseed.has_option('storage', 'install_disks'): if self._preseed.has_option('storage', 'install_disks'):
self._status_callback('preseed storage -> install_disks') self._status_callback('preseed storage -> install_disks')
self.state.add_install_disks(self._preseed_read_json('storage', 'install_disks')) self.state.add_install_disks(self._preseed_read_json('storage', 'install_disks'))
# Process diskusage strategy # Process diskusage strategy
if self.preseed.has_option('storage', 'diskusage_strategy'): if self._preseed.has_option('storage', 'diskusage_strategy'):
self._status_callback('preseed storage -> diskusage_strategy') self._status_callback('preseed storage -> diskusage_strategy')
self.state.diskusage_strategy = self.preseed.get('storage', 'diskusage_strategy').upper() self.state.diskusage_strategy = self._preseed.get('storage', 'diskusage_strategy').upper()
# Process disklayout strategy # Process disklayout strategy
if self.preseed.has_option('storage', 'disklayout_strategy'): if self._preseed.has_option('storage', 'disklayout_strategy'):
self._status_callback('preseed storage -> disklayout_strategy') self._status_callback('preseed storage -> disklayout_strategy')
self.state.disklayout_strategy = self.preseed.get('storage', 'disklayout_strategy').upper() self.state.disklayout_strategy = self._preseed.get('storage', 'disklayout_strategy').upper()
# Process system section # Process system section
if self.preseed.has_section('system'): if self._preseed.has_section('system'):
# Process hostname # Process hostname
if self.preseed.has_option('system', 'hostname'): if self._preseed.has_option('system', 'hostname'):
self._status_callback('preseed system -> hostname') self._status_callback('preseed system -> hostname')
self.state.hostname = self.preseed.get('system', 'hostname') self.state.hostname = self._preseed.get('system', 'hostname')
# Process locale # Process locale
if self.preseed.has_option('system', 'locale'): if self._preseed.has_option('system', 'locale'):
self._status_callback('preseed system -> locale') self._status_callback('preseed system -> locale')
self.state.hostname = self.preseed.get('system', 'locale') self.state.hostname = self._preseed.get('system', 'locale')
# Process timezone # Process timezone
if self.preseed.has_option('system', 'timezone'): if self._preseed.has_option('system', 'timezone'):
self._status_callback('preseed system -> timezone') self._status_callback('preseed system -> timezone')
self.state.hostname = self.preseed.get('system', 'timezone') self.state.hostname = self._preseed.get('system', 'timezone')
# Process install_microcode # Process install_microcode
if self.preseed.has_option('system', 'install_microcode'): if self._preseed.has_option('system', 'install_microcode'):
self._status_callback('preseed system -> install_microcode') self._status_callback('preseed system -> install_microcode')
self.state.hostname = self.preseed.getboolean('system', 'install_microcode') self.state.hostname = self._preseed.getboolean('system', 'install_microcode')
# Process repository section # Process repository section
if self.preseed.has_section('repository'): if self._preseed.has_section('repository'):
# Process mirrorlist # Process mirrorlist
if self.preseed.has_option('repository', 'mirrorlist'): if self._preseed.has_option('repository', 'mirrorlist'):
self._status_callback('preseed repository -> mirrorlist') self._status_callback('preseed repository -> mirrorlist')
self.state.mirrorlist = self._preseed_read_json('repository', 'mirrorlist') self.state.mirrorlist = self._preseed_read_json('repository', 'mirrorlist')
# Process packages # Process packages
if self.preseed.has_option('repository', 'packages'): if self._preseed.has_option('repository', 'packages'):
self._status_callback('preseed repository -> packages') self._status_callback('preseed repository -> packages')
self.state.mirrorlist = self._preseed_read_json('repository', 'mirrorlist') self.state.mirrorlist = self._preseed_read_json('repository', 'mirrorlist')
# Process users section # Process users section
if self.preseed.has_section('users'): if self._preseed.has_section('users'):
# Process root_password # Process root_password
if self.preseed.has_option('users', 'root_password'): if self._preseed.has_option('users', 'root_password'):
self._status_callback('preseed users -> root_password') self._status_callback('preseed users -> root_password')
self.state.root_password = self.preseed.get('users', 'root_password') self.state.root_password = self._preseed.get('users', 'root_password')
# Process user_username # Process user_username
if self.preseed.has_option('users', 'user_username'): if self._preseed.has_option('users', 'user_username'):
self._status_callback('preseed users -> user_username') self._status_callback('preseed users -> user_username')
self.state.user_username = self.preseed.get('users', 'user_username') self.state.user_username = self._preseed.get('users', 'user_username')
# Process user_password # Process user_password
if self.preseed.has_option('users', 'user_password'): if self._preseed.has_option('users', 'user_password'):
self._status_callback('preseed users -> user_password') self._status_callback('preseed users -> user_password')
self.state.user_password = self.preseed.get('users', 'user_password') self.state.user_password = self._preseed.get('users', 'user_password')
# Process user_sshkeys # Process user_sshkeys
if self.preseed.has_option('users', 'user_sshkeys'): if self._preseed.has_option('users', 'user_sshkeys'):
self._status_callback('preseed users -> user_sshkeys') self._status_callback('preseed users -> user_sshkeys')
self.state.user_sshkeys = self._preseed_read_json('users', 'user_sshkeys') self.state.user_sshkeys = self._preseed_read_json('users', 'user_sshkeys')
# Process services section # Process services section
if self.preseed.has_section('services'): if self._preseed.has_section('services'):
# Process enable_services # Process enable_services
if self.preseed.has_option('services', 'enable_services'): if self._preseed.has_option('services', 'enable_services'):
self._status_callback('preseed services -> enable_services') self._status_callback('preseed services -> enable_services')
self.state.mirrorlist = json.loads(self.preseed.get('services', 'enable_services')) self.state.mirrorlist = json.loads(self._preseed.get('services', 'enable_services'))
def _get_install_disks(self): def _get_install_disks(self):
"""Get the user to choose the disks to install on.""" """Get the user to choose the disks to install on."""
...@@ -241,15 +261,15 @@ class Ili: ...@@ -241,15 +261,15 @@ class Ili:
# Loop while no disks were selected # Loop while no disks were selected
while not disks: while not disks:
# Display a checklist with the disks on it # Display a checklist with the disks on it
code, disks = self.dialog.checklist( code, disks = self._dialog.checklist(
'Select disks to install on', 'Select disks to install on',
height=10, width=40, list_height=5, height=10, width=40, list_height=5,
title='Disk Selection', title='Disk Selection',
choices=[(x.path, x.size_str, False) for x in self.block_devices], choices=[(x.path, x.size_str, False) for x in self._block_devices],
) )
# If the user selected cancel, we need to abort # If the user selected cancel, we need to abort
if code == self.dialog.DIALOG_CANCEL: if code == self._dialog.DIALOG_CANCEL:
exit(0) exit(0)
# If no disks were selected, we need to display an error, and do it again # If no disks were selected, we need to display an error, and do it again
...@@ -263,14 +283,14 @@ class Ili: ...@@ -263,14 +283,14 @@ class Ili:
"""Get the disk usage strategy from the user.""" """Get the disk usage strategy from the user."""
# Get available diskusage strategies # Get available diskusage strategies
self.plugins.get_diskusage_strategies(self.state) self._plugins.get_diskusage_strategies(self.state)
chosen_strategy = "" chosen_strategy = ""
# Loop while no disk usage strategy was selected # Loop while no disk usage strategy was selected
while not chosen_strategy: while not chosen_strategy:
# Display a radiolist with the disk usage strategies # Display a radiolist with the disk usage strategies
code, chosen_strategy = self.dialog.radiolist( code, chosen_strategy = self._dialog.radiolist(
'Select disk usage strategy', 'Select disk usage strategy',
height=10, width=40, list_height=5, height=10, width=40, list_height=5,
title='Disk Usage Strategy', title='Disk Usage Strategy',
...@@ -280,7 +300,7 @@ class Ili: ...@@ -280,7 +300,7 @@ class Ili:
) )
# If the user selected cancel, we need to abort # If the user selected cancel, we need to abort
if code == self.dialog.DIALOG_CANCEL: if code == self._dialog.DIALOG_CANCEL:
exit(0) exit(0)
# If no disk usage strategy was selected, we need to display an error, and do it again # If no disk usage strategy was selected, we need to display an error, and do it again
...@@ -294,14 +314,14 @@ class Ili: ...@@ -294,14 +314,14 @@ class Ili:
"""Get disk layout strategy from user.""" """Get disk layout strategy from user."""
# Get available disklayout strategies # Get available disklayout strategies
self.plugins.get_disklayout_strategies(self.state) self._plugins.get_disklayout_strategies(self.state)
chosen_strategy = "" chosen_strategy = ""
# Loop while no disk layout strategy was selected # Loop while no disk layout strategy was selected
while not chosen_strategy: while not chosen_strategy:
# Display a radiolist with the disk layout strategies # Display a radiolist with the disk layout strategies
code, chosen_strategy = self.dialog.radiolist( code, chosen_strategy = self._dialog.radiolist(
'Select disk layout strategy', 'Select disk layout strategy',
height=10, width=40, list_height=5, height=10, width=40, list_height=5,
title='Disk Selection', title='Disk Selection',
...@@ -311,7 +331,7 @@ class Ili: ...@@ -311,7 +331,7 @@ class Ili:
) )
# If the user selected cancel, we need to abort # If the user selected cancel, we need to abort
if code == self.dialog.DIALOG_CANCEL: if code == self._dialog.DIALOG_CANCEL:
exit(0) exit(0)
# If no disks were selected, we need to display an error, and do it again # If no disks were selected, we need to display an error, and do it again
...@@ -329,14 +349,14 @@ class Ili: ...@@ -329,14 +349,14 @@ class Ili:
# Loop while no disk layout strategy was selected # Loop while no disk layout strategy was selected
while not hostname: while not hostname:
# Display an inputbox for the hostname # Display an inputbox for the hostname
code, hostname = self.dialog.inputbox( code, hostname = self._dialog.inputbox(
'Enter system hostname', 'Enter system hostname',
height=8, width=40, height=8, width=40,
title='Hostname', title='Hostname',
) )
# If the user selected cancel, we need to abort # If the user selected cancel, we need to abort
if code == self.dialog.DIALOG_CANCEL: if code == self._dialog.DIALOG_CANCEL:
exit(0) exit(0)
# Set the hostname # Set the hostname
...@@ -348,14 +368,14 @@ class Ili: ...@@ -348,14 +368,14 @@ class Ili:
root_password = "" root_password = ""
# Display an inputbox for the root_password # Display an inputbox for the root_password
code, root_password = self.dialog.passwordbox( code, root_password = self._dialog.passwordbox(
'Enter root password (optional)', 'Enter root password (optional)',
height=8, width=40, insecure=True, height=8, width=40, insecure=True,
title='Root Password', title='Root Password',
) )
# If the user selected cancel, we need to abort # If the user selected cancel, we need to abort
if code == self.dialog.DIALOG_CANCEL: if code == self._dialog.DIALOG_CANCEL:
exit(0) exit(0)
# Set the root password if we got one # Set the root password if we got one
...@@ -368,14 +388,14 @@ class Ili: ...@@ -368,14 +388,14 @@ class Ili:
user_username = "" user_username = ""
# Display an inputbox for the user_username # Display an inputbox for the user_username
code, user_username = self.dialog.inputbox( code, user_username = self._dialog.inputbox(
'Enter extra user username (optional)', 'Enter extra user username (optional)',
height=8, width=40, height=8, width=40,
title='Extra User Username', title='Extra User Username',
) )
# If the user selected cancel, we need to abort # If the user selected cancel, we need to abort
if code == self.dialog.DIALOG_CANCEL: if code == self._dialog.DIALOG_CANCEL:
exit(0) exit(0)
# Set the user username # Set the user username
...@@ -388,14 +408,14 @@ class Ili: ...@@ -388,14 +408,14 @@ class Ili:
user_password = "" user_password = ""
# Display an inputbox for the user_password # Display an inputbox for the user_password
code, user_password = self.dialog.passwordbox( code, user_password = self._dialog.passwordbox(
'Enter user password (optional)', 'Enter user password (optional)',
height=8, width=40, insecure=True, height=8, width=40, insecure=True,
title='User Password', title='User Password',
) )
# If the user selected cancel, we need to abort # If the user selected cancel, we need to abort
if code == self.dialog.DIALOG_CANCEL: if code == self._dialog.DIALOG_CANCEL:
exit(0) exit(0)
# Set the user password if we got one # Set the user password if we got one
...@@ -405,7 +425,7 @@ class Ili: ...@@ -405,7 +425,7 @@ class Ili:
def _preseed_read_json(self, section: str, option: str) -> Any: def _preseed_read_json(self, section: str, option: str) -> Any:
"""Read a ConfigParser option and pass it through json.loads.""" """Read a ConfigParser option and pass it through json.loads."""
# Grab value # Grab value
value = self.preseed.get(section, option) value = self._preseed.get(section, option)
try: try:
return json.loads(value) return json.loads(value)
...@@ -415,7 +435,7 @@ class Ili: ...@@ -415,7 +435,7 @@ class Ili:
def _error(self, title: str, message: str): def _error(self, title: str, message: str):
"""Display error message to user.""" """Display error message to user."""
self.dialog.msgbox( self._dialog.msgbox(
message, message,
title=title, width=70, height=7) title=title, width=70, height=7)
...@@ -425,12 +445,19 @@ class Ili: ...@@ -425,12 +445,19 @@ class Ili:
self._error(*args) self._error(*args)
exit(1) exit(1)
def _status_callback(self, line: str, percent: Optional[int] = None): def _status_callback(self, line: str):
"""Status callback to give user feedback on the status of installation.""" """Status callback to give user feedback on the status of installation."""
# Fix up line
status_line = re.sub('\\s*$', '', line) status_line = re.sub('\\s*$', '', line)
# Ignore blank lines # If its blank, ignore it
if not status_line: if not status_line:
return return
print(f'GOT LINE[{percent}]: {status_line}')
# self.dialog.gauge_update(text=status_line, percent=1, update_text=True) # Check if we're running in text mode
if not self._textmode:
if len(status_line) > 70:
status_line = status_line[0:70] + '...'
self._dialog.gauge_update(text=status_line[0:70], percent=self._percent_done, update_text=True)
else:
print(f'LOG[{self._percent_done}]: {status_line}')