f4d17450 |
# Copyright (C) 2015 vmware inc.
#
# Author: Mahmoud Bassiouny <mbassiouny@vmware.com>
import subprocess
import os
import shutil
import signal
import sys |
4967065a |
import glob
import modules.commons |
f4d17450 |
from jsonwrapper import JsonWrapper
from progressbar import ProgressBar
from window import Window
from actionresult import ActionResult
class Installer(object): |
c533b308 |
def __init__(self, install_config, maxy=0, maxx=0, iso_installer=False,
rpm_path="../stage/RPMS", log_path="../stage/LOGS"): |
f4d17450 |
self.install_config = install_config
self.iso_installer = iso_installer
self.rpm_path = rpm_path
self.log_path = log_path
self.mount_command = "./mk-mount-disk.sh"
self.prepare_command = "./mk-prepare-system.sh"
self.finalize_command = "./mk-finalize-system.sh"
self.chroot_command = "./mk-run-chroot.sh"
self.setup_grub_command = "./mk-setup-grub.sh"
self.unmount_disk_command = "./mk-unmount-disk.sh"
|
cad80a3b |
if 'working_directory' in self.install_config: |
b84da80d |
self.working_directory = self.install_config['working_directory'] |
f4d17450 |
else: |
b84da80d |
self.working_directory = "/mnt/photon-root" |
c533b308 |
self.photon_root = self.working_directory + "/photon-chroot" |
a5a8bd39 |
self.rpms_tobeinstalled = None |
f4d17450 |
self.restart_command = "shutdown"
if self.iso_installer:
self.output = open(os.devnull, 'w')
else:
self.output = None
if self.iso_installer:
#initializing windows
self.maxy = maxy
self.maxx = maxx
self.height = 10
self.width = 75
self.progress_padding = 5
self.progress_width = self.width - self.progress_padding |
0c250142 |
self.starty = (self.maxy - self.height) // 2
self.startx = (self.maxx - self.width) // 2 |
c533b308 |
self.window = Window(self.height, self.width, self.maxy, self.maxx,
'Installing Photon', False)
self.progress_bar = ProgressBar(self.starty + 3,
self.startx + self.progress_padding // 2,
self.progress_width) |
f4d17450 |
signal.signal(signal.SIGINT, self.exit_gracefully)
# This will be called if the installer interrupted by Ctrl+C or exception |
a5a8bd39 |
def exit_gracefully(self, signal1, frame1):
del signal1
del frame1 |
f4d17450 |
if self.iso_installer:
self.progress_bar.hide() |
c533b308 |
self.window.addstr(0, 0, 'Oops, Installer got interrupted.\n\n' +
'Press any key to get to the bash...') |
f4d17450 |
self.window.content_window().getch() |
022959f9 |
|
cad80a3b |
modules.commons.dump(modules.commons.LOG_FILE_NAME) |
f4d17450 |
sys.exit(1)
def install(self, params): |
a5a8bd39 |
del params |
f4d17450 |
try: |
a5a8bd39 |
return self.unsafe_install() |
34dfc7d7 |
except Exception as inst: |
f4d17450 |
if self.iso_installer: |
34dfc7d7 |
modules.commons.log(modules.commons.LOG_ERROR, repr(inst)) |
f4d17450 |
self.exit_gracefully(None, None)
else:
raise
|
a5a8bd39 |
def unsafe_install(self):
self.setup_install_repo() |
4967065a |
self.execute_modules(modules.commons.PRE_INSTALL)
|
b84da80d |
self.initialize_system() |
a5a8bd39 |
self.install_packages()
self.enable_network_in_chroot() |
f4d17450 |
self.finalize_system()
|
b84da80d |
if not self.install_config['iso_system']: |
4967065a |
# Execute post installation modules
self.execute_modules(modules.commons.POST_INSTALL) |
c9711d2f |
if os.path.exists(modules.commons.KS_POST_INSTALL_LOG_FILE_NAME): |
c533b308 |
shutil.copy(modules.commons.KS_POST_INSTALL_LOG_FILE_NAME,
self.photon_root + '/var/log/') |
4967065a |
|
a6e91563 |
if self.iso_installer and os.path.isdir("/sys/firmware/efi"):
self.install_config['boot'] = 'efi' |
f4d17450 |
# install grub |
85904234 |
if 'boot_partition_number' not in self.install_config['disk']:
self.install_config['disk']['boot_partition_number'] = 1
|
7fd875e7 |
try:
if self.install_config['boot'] == 'bios': |
85904234 |
process = subprocess.Popen( |
c533b308 |
[self.setup_grub_command, '-w', self.photon_root,
"bios", self.install_config['disk']['disk'],
self.install_config['disk']['root'],
self.install_config['disk']['boot'],
self.install_config['disk']['bootdirectory'],
str(self.install_config['disk']['boot_partition_number'])],
stdout=self.output) |
7fd875e7 |
elif self.install_config['boot'] == 'efi': |
85904234 |
process = subprocess.Popen( |
c533b308 |
[self.setup_grub_command, '-w', self.photon_root,
"efi", self.install_config['disk']['disk'],
self.install_config['disk']['root'],
self.install_config['disk']['boot'],
self.install_config['disk']['bootdirectory'],
str(self.install_config['disk']['boot_partition_number'])],
stdout=self.output) |
7fd875e7 |
except:
#install bios if variable is not set. |
85904234 |
process = subprocess.Popen( |
c533b308 |
[self.setup_grub_command, '-w', self.photon_root,
"bios", self.install_config['disk']['disk'],
self.install_config['disk']['root'],
self.install_config['disk']['boot'],
self.install_config['disk']['bootdirectory'],
str(self.install_config['disk']['boot_partition_number'])],
stdout=self.output) |
f4d17450 |
retval = process.wait()
|
c3771c35 |
self.update_fstab() |
a5a8bd39 |
self.disable_network_in_chroot() |
20741901 |
|
c3771c35 |
command = [self.unmount_disk_command, '-w', self.photon_root]
if not self.install_config['iso_system']: |
c533b308 |
command.extend(self.generate_partitions_param(reverse=True)) |
c3771c35 |
process = subprocess.Popen(command, stdout=self.output) |
f4d17450 |
retval = process.wait()
if self.iso_installer:
self.progress_bar.hide() |
a5a8bd39 |
self.window.addstr(0, 0, 'Congratulations, Photon has been installed in {0} secs.\n\n' |
c533b308 |
'Press any key to continue to boot...'
.format(self.progress_bar.time_elapsed)) |
a5a8bd39 |
self.eject_cdrom() |
f4d17450 |
return ActionResult(True, None) |
c533b308 |
|
b84da80d |
def copy_rpms(self):
# prepare the RPMs list |
b944098f |
json_pkg_to_rpm_map = JsonWrapper(self.install_config["pkg_to_rpm_map_file"])
pkg_to_rpm_map = json_pkg_to_rpm_map.read() |
f4d17450 |
self.rpms_tobeinstalled = []
selected_packages = self.install_config['packages'] |
b944098f |
|
c533b308 |
for pkg in selected_packages: |
b944098f |
if pkg in pkg_to_rpm_map: |
a5a8bd39 |
if pkg_to_rpm_map[pkg]['rpm'] is not None: |
b944098f |
name = pkg_to_rpm_map[pkg]['rpm']
basename = os.path.basename(name) |
c533b308 |
self.rpms_tobeinstalled.append({'filename': basename, 'path': name,
'package' : pkg}) |
b944098f |
|
b84da80d |
# Copy the rpms |
f4d17450 |
for rpm in self.rpms_tobeinstalled: |
b84da80d |
shutil.copy(rpm['path'], self.photon_root + '/RPMS/') |
f4d17450 |
|
b84da80d |
def copy_files(self):
# Make the photon_root directory if not exits
process = subprocess.Popen(['mkdir', '-p', self.photon_root], stdout=self.output)
retval = process.wait()
# Copy the installer files |
c533b308 |
process = subprocess.Popen(['cp', '-r', "../installer", self.photon_root],
stdout=self.output) |
b84da80d |
retval = process.wait()
|
bffb7f74 |
# Create the rpms directory |
c533b308 |
process = subprocess.Popen(['mkdir', '-p', self.photon_root + '/RPMS'],
stdout=self.output) |
bffb7f74 |
retval = process.wait() |
6860f77c |
self.copy_rpms() |
bffb7f74 |
|
6860f77c |
def bind_installer(self):
# Make the photon_root/installer directory if not exits |
c533b308 |
process = subprocess.Popen(['mkdir', '-p', os.path.join(self.photon_root, "installer")],
stdout=self.output) |
6860f77c |
retval = process.wait() |
c533b308 |
# The function finalize_system will access the file /installer/mk-finalize-system.sh
# after chroot to photon_root.
# Bind the /installer folder to self.photon_root/installer, so that after chroot
# to photon_root, |
6860f77c |
# the file can still be accessed as /installer/mk-finalize-system.sh. |
c533b308 |
process = subprocess.Popen(['mount', '--bind', '/installer',
os.path.join(self.photon_root, "installer")],
stdout=self.output) |
6860f77c |
retval = process.wait() |
b84da80d |
|
9d42f28e |
def bind_repo_dir(self):
rpm_cache_dir = self.photon_root + '/cache/tdnf/photon-iso/rpms' |
f5cac196 |
if self.rpm_path.startswith("https://") or self.rpm_path.startswith("http://"):
return |
c533b308 |
if (subprocess.call(['mkdir', '-p', rpm_cache_dir]) != 0 or
subprocess.call(['mount', '--bind', self.rpm_path, rpm_cache_dir]) != 0): |
9d42f28e |
modules.commons.log(modules.commons.LOG_ERROR, "Fail to bind cache rpms")
self.exit_gracefully(None, None) |
2f40f1d3 |
def unbind_repo_dir(self):
rpm_cache_dir = self.photon_root + '/cache/tdnf/photon-iso/rpms'
if self.rpm_path.startswith("https://") or self.rpm_path.startswith("http://"):
return |
c533b308 |
if (subprocess.call(['umount', rpm_cache_dir]) != 0 or
subprocess.call(['rm', '-rf', rpm_cache_dir]) != 0): |
2f40f1d3 |
modules.commons.log(modules.commons.LOG_ERROR, "Fail to unbind cache rpms")
self.exit_gracefully(None, None) |
9d42f28e |
|
c3771c35 |
def update_fstab(self): |
e51de717 |
with open(os.path.join(self.photon_root, "etc/fstab"), "w") as fstab_file:
fstab_file.write("#system\tmnt-pt\ttype\toptions\tdump\tfsck\n") |
c3771c35 |
|
e51de717 |
for partition in self.install_config['disk']['partitions']:
options = 'defaults'
dump = 1
fsck = 2 |
c3771c35 |
|
e51de717 |
if 'mountpoint' in partition and partition['mountpoint'] == '/':
options = options + ',barrier,noatime,noacl,data=ordered'
fsck = 1 |
c533b308 |
|
e51de717 |
if partition['filesystem'] == 'swap':
mountpoint = 'swap'
dump = 0
fsck = 0
else:
mountpoint = partition['mountpoint']
fstab_file.write("{}\t{}\t{}\t{}\t{}\t{}\n".format(
partition['path'],
mountpoint,
partition['filesystem'],
options,
dump,
fsck
))
# Add the cdrom entry
fstab_file.write("/dev/cdrom\t/mnt/cdrom\tiso9660\tro,noauto\t0\t0\n") |
c3771c35 |
|
c533b308 |
def generate_partitions_param(self, reverse=False): |
c3771c35 |
if reverse:
step = -1
else:
step = 1
params = []
for partition in self.install_config['disk']['partitions'][::step]:
if partition["filesystem"] == "swap":
continue
params.extend(['--partitionmountpoint', partition["path"], partition["mountpoint"]])
return params
|
b84da80d |
def initialize_system(self): |
f4d17450 |
#Setup the disk |
c533b308 |
if not self.install_config['iso_system']: |
c3771c35 |
command = [self.mount_command, '-w', self.photon_root]
command.extend(self.generate_partitions_param())
process = subprocess.Popen(command, stdout=self.output) |
f4d17450 |
retval = process.wait() |
c533b308 |
|
6860f77c |
if self.iso_installer:
self.bind_installer() |
9d42f28e |
self.bind_repo_dir() |
c533b308 |
process = subprocess.Popen([self.prepare_command, '-w', self.photon_root, 'install'],
stdout=self.output) |
6860f77c |
retval = process.wait()
else:
self.copy_files()
#Setup the filesystem basics |
c533b308 |
process = subprocess.Popen([self.prepare_command, '-w', self.photon_root],
stdout=self.output) |
6860f77c |
retval = process.wait() |
cad80a3b |
|
f4d17450 |
def finalize_system(self):
#Setup the disk |
c533b308 |
process = subprocess.Popen([self.chroot_command, '-w', self.photon_root,
self.finalize_command, '-w', self.photon_root],
stdout=self.output) |
f4d17450 |
retval = process.wait()
if self.iso_installer: |
562cb584 |
modules.commons.dump(modules.commons.LOG_FILE_NAME)
shutil.copy(modules.commons.LOG_FILE_NAME, self.photon_root + '/var/log/') |
c533b308 |
shutil.copy(modules.commons.TDNF_LOG_FILE_NAME, self.photon_root +
'/var/log/') |
562cb584 |
|
6860f77c |
# unmount the installer directory |
c533b308 |
process = subprocess.Popen(['umount', os.path.join(self.photon_root,
"installer")],
stdout=self.output) |
6860f77c |
retval = process.wait() |
e9ff2627 |
# remove the installer directory |
c533b308 |
process = subprocess.Popen(['rm', '-rf', os.path.join(self.photon_root, "installer")],
stdout=self.output) |
e9ff2627 |
retval = process.wait() |
2f40f1d3 |
self.unbind_repo_dir() |
adde0296 |
# Disable the swap file
process = subprocess.Popen(['swapoff', '-a'], stdout=self.output) |
f4d17450 |
retval = process.wait() |
adde0296 |
# remove the tdnf cache directory and the swapfile. |
c533b308 |
process = subprocess.Popen(['rm', '-rf', os.path.join(self.photon_root, "cache")],
stdout=self.output) |
f5cac196 |
retval = process.wait() |
f4d17450 |
|
c533b308 |
def install_package(self, rpm_file_names): |
85b82155 |
|
f5cac196 |
rpms = set(rpm_file_names)
rpm_paths = [] |
a5a8bd39 |
for root, _, files in os.walk(self.rpm_path): |
f5cac196 |
for f in files:
if f in rpms:
rpm_paths.append(os.path.join(root, f)) |
85b82155 |
|
0f1fdc4b |
# --nodeps is for hosts which do not support rich dependencies |
c533b308 |
rpm_params = ['--nodeps', '--root', self.photon_root, '--dbpath',
'/var/lib/rpm'] |
f4d17450 |
|
c533b308 |
if (('type' in self.install_config and
(self.install_config['type'] in ['micro', 'minimal'])) or
self.install_config['iso_system']): |
f5cac196 |
rpm_params.append('--excludedocs') |
85b82155 |
|
c533b308 |
modules.commons.log(modules.commons.LOG_INFO,
"installing packages {0}, with params {1}"
.format(rpm_paths, rpm_params))
process = subprocess.Popen(['rpm', '-Uvh'] + rpm_params + rpm_paths,
stderr=subprocess.STDOUT) |
f4d17450 |
return process.wait()
|
4967065a |
def execute_modules(self, phase): |
a672bd24 |
sys.path.append("./modules") |
022959f9 |
modules_paths = glob.glob('modules/m_*.py')
for mod_path in modules_paths: |
4967065a |
module = mod_path.replace('/', '.', 1)
module = os.path.splitext(module)[0]
try:
__import__(module)
mod = sys.modules[module]
except ImportError: |
c533b308 |
modules.commons.log(modules.commons.LOG_ERROR,
'Error importing module {}'.format(module)) |
4967065a |
continue |
c533b308 |
|
4967065a |
# the module default is disabled |
a5a8bd39 |
if not hasattr(mod, 'enabled') or mod.enabled is False: |
c533b308 |
modules.commons.log(modules.commons.LOG_INFO,
"module {} is not enabled".format(module)) |
4967065a |
continue
# check for the install phase
if not hasattr(mod, 'install_phase'): |
c533b308 |
modules.commons.log(modules.commons.LOG_ERROR,
"Error: can not defind module {} phase".format(module)) |
4967065a |
continue
if mod.install_phase != phase: |
c533b308 |
modules.commons.log(modules.commons.LOG_INFO,
"Skipping module {0} for phase {1}".format(module, phase)) |
4967065a |
continue
if not hasattr(mod, 'execute'): |
c533b308 |
modules.commons.log(modules.commons.LOG_ERROR,
"Error: not able to execute module {}".format(module)) |
4967065a |
continue |
a672bd24 |
|
a5a8bd39 |
mod.execute(self.install_config, self.photon_root) |
4d53ba03 |
|
564d9533 |
def adjust_packages_for_vmware_virt(self):
try: |
36a312f8 |
if self.install_config['install_linux_esx']: |
564d9533 |
selected_packages = self.install_config['packages']
try:
selected_packages.remove('linux')
except ValueError:
pass
try:
selected_packages.remove('initramfs')
except ValueError:
pass
selected_packages.append('linux-esx')
except KeyError:
pass |
a5a8bd39 |
def setup_install_repo(self):
if self.iso_installer:
self.window.show_window()
self.progress_bar.initialize('Initializing installation...')
self.progress_bar.show()
#self.rpm_path = "https://dl.bintray.com/vmware/photon_release_1.0_TP2_x86_64"
if self.rpm_path.startswith("https://") or self.rpm_path.startswith("http://"):
cmdoption = 's/baseurl.*/baseurl={}/g'.format(self.rpm_path.replace('/', '\/'))
process = subprocess.Popen(['sed', '-i', cmdoption,
'/etc/yum.repos.d/photon-iso.repo'])
retval = process.wait()
if retval != 0:
modules.commons.log(modules.commons.LOG_INFO, "Failed to reset repo")
self.exit_gracefully(None, None)
cmdoption = ('s/cachedir=\/var/cachedir={}/g'
.format(self.photon_root.replace('/', '\/')))
process = subprocess.Popen(['sed', '-i', cmdoption, '/etc/tdnf/tdnf.conf'])
retval = process.wait()
if retval != 0:
modules.commons.log(modules.commons.LOG_INFO, "Failed to reset tdnf cachedir")
self.exit_gracefully(None, None)
def install_packages(self):
if self.iso_installer:
self.tdnf_install_packages()
else:
#install packages
rpms = []
for rpm in self.rpms_tobeinstalled:
# We already installed the filesystem in the preparation
if rpm['package'] == 'filesystem':
continue
rpms.append(rpm['filename'])
return_value = self.install_package(rpms)
if return_value != 0:
self.exit_gracefully(None, None)
def tdnf_install_packages(self):
self.adjust_packages_for_vmware_virt()
selected_packages = self.install_config['packages']
state = 0
packages_to_install = {}
total_size = 0
with open(modules.commons.TDNF_CMDLINE_FILE_NAME, "w") as tdnf_cmdline_file:
tdnf_cmdline_file.write("tdnf install --installroot {0} --nogpgcheck {1}"
.format(self.photon_root, " ".join(selected_packages)))
with open(modules.commons.TDNF_LOG_FILE_NAME, "w") as tdnf_errlog:
process = subprocess.Popen(['tdnf', 'install'] + selected_packages +
['--installroot', self.photon_root, '--nogpgcheck',
'--assumeyes'], stdout=subprocess.PIPE,
stderr=tdnf_errlog)
while True:
output = process.stdout.readline().decode()
if output == '':
retval = process.poll()
if retval is not None:
break
if state == 0:
if output == 'Installing:\n':
state = 1
elif state == 1: #N A EVR Size(readable) Size(in bytes)
if output == '\n':
state = 2
self.progress_bar.update_num_items(total_size)
else:
info = output.split()
package = '{0}-{1}.{2}'.format(info[0], info[2], info[1])
packages_to_install[package] = int(info[5])
total_size += int(info[5])
elif state == 2:
if output == 'Downloading:\n':
self.progress_bar.update_message('Preparing ...')
state = 3
elif state == 3:
self.progress_bar.update_message(output)
if output == 'Running transaction\n':
state = 4
else:
modules.commons.log(modules.commons.LOG_INFO, "[tdnf] {0}".format(output))
prefix = 'Installing/Updating: '
if output.startswith(prefix):
package = output[len(prefix):].rstrip('\n')
self.progress_bar.increment(packages_to_install[package])
self.progress_bar.update_message(output)
# 0 : succeed; 137 : package already installed; 65 : package not found in repo.
if retval != 0 and retval != 137:
modules.commons.log(modules.commons.LOG_ERROR,
"Failed to install some packages, refer to {0}"
.format(modules.commons.TDNF_LOG_FILE_NAME))
self.exit_gracefully(None, None)
self.progress_bar.show_loading('Finalizing installation')
def eject_cdrom(self):
eject_cdrom = True
if 'ui_install' in self.install_config:
self.window.content_window().getch()
if 'eject_cdrom' in self.install_config and not self.install_config['eject_cdrom']:
eject_cdrom = False
if eject_cdrom:
process = subprocess.Popen(['eject', '-r'], stdout=self.output)
process.wait()
def enable_network_in_chroot(self):
if os.path.exists("/etc/resolv.conf"):
shutil.copy("/etc/resolv.conf", self.photon_root + '/etc/.')
def disable_network_in_chroot(self):
if os.path.exists(self.photon_root + '/etc/resolv.conf'):
os.remove(self.photon_root + '/etc/resolv.conf') |