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

Merge branch 'nkupdates' into 'master'

Nkupdates

See merge request idms-linux-rolling/projects/idms-linux-installer!7
parents 5d5f8f2c 66caa6ae
......@@ -31,6 +31,8 @@ def main():
installer_group = argparser.add_argument_group('Installer options')
installer_group.add_argument('--preseed-uri', dest='preseed_uri', action='store',
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
optional_group = argparser.add_argument_group('Optional arguments')
optional_group.add_argument('-h', '--help', action="help", help="Show this help message and exit")
......@@ -43,6 +45,8 @@ def main():
if args.preseed_uri:
kwargs['preseed_uri'] = args.preseed_uri
if args.textmode:
kwargs['textmode'] = args.textmode
# Fire up the installer
installer = Ili()
......
......@@ -38,28 +38,38 @@ class Ili:
"""
# 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
preseed: ConfigParser
# This is the state of the installation
state: IliState
_preseed: ConfigParser
# Plugins
plugins: PluginCollection
_plugins: PluginCollection
# Block devices on the system
block_devices: BlockDevices
_block_devices: BlockDevices
# This is the state of the installation
state: IliState
def __init__(self):
"""Initialize the object."""
# Load plugins
self.plugins = PluginCollection('idmslinux_installer.plugins')
# Fire up Dialog
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
self.preseed = None
self._preseed = None
# Load plugins
self._plugins = PluginCollection('idmslinux_installer.plugins')
# Initialize the install state
self.state = IliState()
......@@ -67,24 +77,23 @@ class Ili:
# Blank the locale
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
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."""
# Load preseed URI if it was provided
if preseed_uri:
self._status_callback('Downloading preseed "{preseed_uri}"...')
self._status_callback(f'Downloading 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:
# Load the system block devices
self.block_devices = BlockDevices()
self._block_devices = BlockDevices()
self._get_install_disks()
if not self.state.diskusage_strategy:
......@@ -111,19 +120,31 @@ class Ili:
if self.state.user_username:
self._get_user_password()
# self.dialog.gauge_start("Preparing", title="Installing...", width=75, height=7)
# We always call these
self.plugins.commit_diskusage_strategy(self.state)
self.plugins.commit_disklayout_strategy(self.state)
self.plugins.mount_filesystems(self.state)
self.plugins.pre_install_base(self.state)
self.plugins.install_base(self.state)
self.plugins.config_system(self.state)
self.plugins.install_packages(self.state)
self.plugins.post_install(self.state)
# self.dialog.gauge_stop()
if not self._textmode:
self._dialog.gauge_start("Preparing...", title="Installing...", width=75, height=7)
# Loop with install steps
steps = [
self._plugins.commit_diskusage_strategy,
self._plugins.commit_disklayout_strategy,
self._plugins.mount_filesystems,
self._plugins.pre_install_base,
self._plugins.install_base,
self._plugins.config_system,
self._plugins.install_packages,
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
# R0912 - Ignore warning that we have about the number of branches
......@@ -133,7 +154,7 @@ class Ili:
"""Load installer configuration from preseed file."""
# Parse config
self.preseed = ConfigParser()
self._preseed = ConfigParser()
# HTTP
if re.match('^https?://', preseed_uri):
......@@ -157,81 +178,78 @@ class Ili:
self._critical_error('Error with preseed URI', f'Preseed URI type is not supported:\n{preseed_uri}')
# Read config as string
self.preseed.read_string(config_data)
sects = self.preseed.sections()
print(f'SECTIONS: {sects}')
self._preseed.read_string(config_data)
# Process storage section
if self.preseed.has_section('storage'):
if self._preseed.has_section('storage'):
# 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.state.add_install_disks(self._preseed_read_json('storage', 'install_disks'))
# 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.state.diskusage_strategy = self.preseed.get('storage', 'diskusage_strategy').upper()
self.state.diskusage_strategy = self._preseed.get('storage', 'diskusage_strategy').upper()
# 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.state.disklayout_strategy = self.preseed.get('storage', 'disklayout_strategy').upper()
self.state.disklayout_strategy = self._preseed.get('storage', 'disklayout_strategy').upper()
# Process system section
if self.preseed.has_section('system'):
if self._preseed.has_section('system'):
# Process hostname
if self.preseed.has_option('system', 'hostname'):
if self._preseed.has_option('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
if self.preseed.has_option('system', 'locale'):
if self._preseed.has_option('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
if self.preseed.has_option('system', 'timezone'):
if self._preseed.has_option('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
if self.preseed.has_option('system', 'install_microcode'):
if self._preseed.has_option('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
if self.preseed.has_section('repository'):
if self._preseed.has_section('repository'):
# Process mirrorlist
if self.preseed.has_option('repository', 'mirrorlist'):
if self._preseed.has_option('repository', 'mirrorlist'):
self._status_callback('preseed repository -> mirrorlist')
self.state.mirrorlist = self._preseed_read_json('repository', 'mirrorlist')
# Process packages
if self.preseed.has_option('repository', 'packages'):
if self._preseed.has_option('repository', 'packages'):
self._status_callback('preseed repository -> packages')
self.state.mirrorlist = self._preseed_read_json('repository', 'mirrorlist')
# Process users section
if self.preseed.has_section('users'):
if self._preseed.has_section('users'):
# 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.state.root_password = self.preseed.get('users', 'root_password')
self.state.root_password = self._preseed.get('users', 'root_password')
# 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.state.user_username = self.preseed.get('users', 'user_username')
self.state.user_username = self._preseed.get('users', 'user_username')
# 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.state.user_password = self.preseed.get('users', 'user_password')
self.state.user_password = self._preseed.get('users', 'user_password')
# 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.state.user_sshkeys = self._preseed_read_json('users', 'user_sshkeys')
# Process services section
if self.preseed.has_section('services'):
if self._preseed.has_section('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.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):
"""Get the user to choose the disks to install on."""
......@@ -241,15 +259,15 @@ class Ili:
# Loop while no disks were selected
while not disks:
# Display a checklist with the disks on it
code, disks = self.dialog.checklist(
code, disks = self._dialog.checklist(
'Select disks to install on',
height=10, width=40, list_height=5,
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 code == self.dialog.DIALOG_CANCEL:
if code == self._dialog.DIALOG_CANCEL:
exit(0)
# If no disks were selected, we need to display an error, and do it again
......@@ -263,14 +281,14 @@ class Ili:
"""Get the disk usage strategy from the user."""
# Get available diskusage strategies
self.plugins.get_diskusage_strategies(self.state)
self._plugins.get_diskusage_strategies(self.state)
chosen_strategy = ""
# Loop while no disk usage strategy was selected
while not chosen_strategy:
# Display a radiolist with the disk usage strategies
code, chosen_strategy = self.dialog.radiolist(
code, chosen_strategy = self._dialog.radiolist(
'Select disk usage strategy',
height=10, width=40, list_height=5,
title='Disk Usage Strategy',
......@@ -280,7 +298,7 @@ class Ili:
)
# If the user selected cancel, we need to abort
if code == self.dialog.DIALOG_CANCEL:
if code == self._dialog.DIALOG_CANCEL:
exit(0)
# If no disk usage strategy was selected, we need to display an error, and do it again
......@@ -294,14 +312,14 @@ class Ili:
"""Get disk layout strategy from user."""
# Get available disklayout strategies
self.plugins.get_disklayout_strategies(self.state)
self._plugins.get_disklayout_strategies(self.state)
chosen_strategy = ""
# Loop while no disk layout strategy was selected
while not chosen_strategy:
# Display a radiolist with the disk layout strategies
code, chosen_strategy = self.dialog.radiolist(
code, chosen_strategy = self._dialog.radiolist(
'Select disk layout strategy',
height=10, width=40, list_height=5,
title='Disk Selection',
......@@ -311,7 +329,7 @@ class Ili:
)
# If the user selected cancel, we need to abort
if code == self.dialog.DIALOG_CANCEL:
if code == self._dialog.DIALOG_CANCEL:
exit(0)
# If no disks were selected, we need to display an error, and do it again
......@@ -329,14 +347,14 @@ class Ili:
# Loop while no disk layout strategy was selected
while not hostname:
# Display an inputbox for the hostname
code, hostname = self.dialog.inputbox(
code, hostname = self._dialog.inputbox(
'Enter system hostname',
height=8, width=40,
title='Hostname',
)
# If the user selected cancel, we need to abort
if code == self.dialog.DIALOG_CANCEL:
if code == self._dialog.DIALOG_CANCEL:
exit(0)
# Set the hostname
......@@ -348,14 +366,14 @@ class Ili:
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)',
height=8, width=40, insecure=True,
title='Root Password',
)
# If the user selected cancel, we need to abort
if code == self.dialog.DIALOG_CANCEL:
if code == self._dialog.DIALOG_CANCEL:
exit(0)
# Set the root password if we got one
......@@ -368,14 +386,14 @@ class Ili:
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)',
height=8, width=40,
title='Extra User Username',
)
# If the user selected cancel, we need to abort
if code == self.dialog.DIALOG_CANCEL:
if code == self._dialog.DIALOG_CANCEL:
exit(0)
# Set the user username
......@@ -388,14 +406,14 @@ class Ili:
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)',
height=8, width=40, insecure=True,
title='User Password',
)
# If the user selected cancel, we need to abort
if code == self.dialog.DIALOG_CANCEL:
if code == self._dialog.DIALOG_CANCEL:
exit(0)
# Set the user password if we got one
......@@ -405,7 +423,7 @@ class Ili:
def _preseed_read_json(self, section: str, option: str) -> Any:
"""Read a ConfigParser option and pass it through json.loads."""
# Grab value
value = self.preseed.get(section, option)
value = self._preseed.get(section, option)
try:
return json.loads(value)
......@@ -415,7 +433,7 @@ class Ili:
def _error(self, title: str, message: str):
"""Display error message to user."""
self.dialog.msgbox(
self._dialog.msgbox(
message,
title=title, width=70, height=7)
......@@ -425,12 +443,19 @@ class Ili:
self._error(*args)
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."""
# Fix up line
status_line = re.sub('\\s*$', '', line)
# Ignore blank lines
# If its blank, ignore it
if not status_line:
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}')
......@@ -454,7 +454,6 @@ class IliState:
# Loop with any mounts we have and unmount them
for mount in reversed(self._mounts):
print(f'UNMOUNTING: {mount}')
# Unmount and remove
mount.umount()
self._mounts.remove(mount)
......
......@@ -215,7 +215,6 @@ class PluginCollection:
if pkg_path not in self.seen_paths:
# If not add it so we don't process it again
self.seen_paths.append(pkg_path)
print(f'PACKAGE PATH: {pkg_path}')
# Grab all the sub directories of the current package path directory
sub_dirs = [p for p in os.listdir(pkg_path)
if os.path.isdir(os.path.join(pkg_path, p))]
......
......@@ -40,4 +40,4 @@ class PostInstallServices(Plugin):
# Check if we have services to enable
if ili_state.enable_services:
ili_state.output_callback('Enabling services')
systemd.enable_service(ili_state.target_root, ili_state._enable_services)
systemd.enable_service(ili_state.target_root, ili_state._enable_services, output_callback=ili_state.output_callback)
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment