Esempio n. 1
0
class OstreeInstaller(object):
    def __init__(self, installer):
        self.repo_config = {}
        self.installer_path = installer.installer_path
        self.repo_read_conf()

        # simulate inheritance
        self.install_config = installer.install_config
        self.logger = installer.logger
        if self.install_config['ui']:
            self.progress_bar = installer.progress_bar
        self.photon_root = installer.photon_root
        self._create_fstab = installer._create_fstab
        self.exit_gracefully = installer.exit_gracefully
        self._get_uuid = installer._get_uuid
        self._get_partuuid = installer._get_partuuid
        self.cmd = CommandUtils(self.logger)

    def get_ostree_repo_url(self):
        self.default_repo = self.install_config['ostree'].get(
            'default_repo', False)
        if not self.default_repo:
            self.ostree_repo_url = self.install_config['ostree']['repo_url']
            self.ostree_ref = self.install_config['ostree']['repo_ref']

    def repo_read_conf(self):
        conf_path = os.path.abspath(self.installer_path +
                                    "/ostree-release-repo.conf")
        with open(conf_path) as repo_conf:
            for line in repo_conf:
                name, value = line.partition("=")[::2]
                self.repo_config[name] = value.strip(' \n\t\r')

    def pull_repo(self, repo_url, repo_ref):
        self.run([[
            'ostree', 'remote', 'add', '--repo={}/ostree/repo'.format(
                self.photon_root), '--set=gpg-verify=false', 'photon',
            '{}'.format(repo_url)
        ]], "Adding OSTree remote")
        if self.default_repo:
            self.run([[
                'ostree', 'pull-local', '--repo={}/ostree/repo'.format(
                    self.photon_root), '{}'.format(self.local_repo_path)
            ]], "Pulling OSTree repo")
            cmd = []
            cmd.append([
                'mv', '{}/ostree/repo/refs/heads'.format(self.photon_root),
                '{}/ostree/repo/refs/remotes/photon'.format(self.photon_root)
            ])
            cmd.append([
                'mkdir', '-p',
                '{}/ostree/repo/refs/heads'.format(self.photon_root)
            ])
            self.run(cmd)
        else:
            self.run([[
                'ostree', 'pull', '--repo={}/ostree/repo'.format(
                    self.photon_root), 'photon', '{}'.format(repo_ref)
            ]], "Pulling OSTree remote repo")

    def deploy_ostree(self, repo_url, repo_ref):
        self.run([[
            'ostree', 'admin', '--sysroot={}'.format(self.photon_root),
            'init-fs', '{}'.format(self.photon_root)
        ]], "Initializing OSTree filesystem")
        self.pull_repo(repo_url, repo_ref)
        self.run([[
            'ostree', 'admin', '--sysroot={}'.format(self.photon_root),
            'os-init', 'photon'
        ]], "OSTree OS Initializing")
        self.run([[
            'ostree', 'admin', '--sysroot={}'.format(self.photon_root),
            'deploy', '--os=photon', 'photon:{}'.format(repo_ref)
        ]], "Deploying")

    def do_systemd_tmpfiles_commands(self, commit_number):
        prefixes = [
            "/var/home", "/var/roothome", "/var/lib/rpm", "/var/opt",
            "/var/srv", "/var/userlocal", "/var/mnt", "/var/spool/mail"
        ]

        for prefix in prefixes:
            command = [
                'systemd-tmpfiles', '--create', '--boot',
                '--root={}/ostree/deploy/photon/deploy/{}.0'.format(
                    self.photon_root,
                    commit_number), '--prefix={}'.format(prefix)
            ]
            self.run([command], "systemd-tmpfiles command done")

    def create_symlink_directory(self, deployment):
        command = []
        command.append(['mkdir', '-p', '{}/sysroot/tmp'.format(deployment)])
        command.append(['mkdir', '-p', '{}/sysroot/ostree'.format(deployment)])
        command.append(['mkdir', '-p', '{}/run/media'.format(deployment)])
        self.run(command, "symlink directory created")

    def mount_devices_in_deployment(self, commit_number):
        command = []
        command.append([
            'mount', '-t', 'bind', '-o', 'bind,defaults', '/dev',
            '{}/ostree/deploy/photon/deploy/{}.0/dev'.format(
                self.photon_root, commit_number)
        ])
        command.append([
            'mount', '-t', 'devpts', '-o', 'gid=5,mode=620', 'devpts',
            '{}/ostree/deploy/photon/deploy/{}.0/dev/pts'.format(
                self.photon_root, commit_number)
        ])
        command.append([
            'mount', '-t', 'tmpfs', '-o', 'defaults', 'tmpfs',
            '{}/ostree/deploy/photon/deploy/{}.0/dev/shm'.format(
                self.photon_root, commit_number)
        ])
        command.append([
            'mount', '-t', 'proc', '-o', 'defaults', 'proc',
            '{}/ostree/deploy/photon/deploy/{}.0/proc'.format(
                self.photon_root, commit_number)
        ])
        command.append([
            'mount', '-t', 'bind', '-o', 'bind,defaults', '/run',
            '{}/ostree/deploy/photon/deploy/{}.0/run'.format(
                self.photon_root, commit_number)
        ])
        command.append([
            'mount', '-t', 'sysfs', '-o', 'defaults', 'sysfs',
            '{}/ostree/deploy/photon/deploy/{}.0/sys'.format(
                self.photon_root, commit_number)
        ])
        self.run(command, "mounting done")

    def get_commit_number(self, ref):
        fileName = os.path.join(
            self.photon_root, "ostree/repo/refs/remotes/photon/{}".format(ref))
        commit_number = None
        with open(fileName, "r") as file:
            commit_number = file.read().replace('\n', '')
        return commit_number

    def install(self):
        """
        Ostree Installer  Main
        """
        partition_data = {}
        for partition in self.install_config['partitions']:
            if partition.get('mountpoint', '') == '/boot':
                partition_data[
                    'boot'] = self.photon_root + partition['mountpoint']
                partition_data['bootdirectory'] = partition['mountpoint']
            if partition.get(
                    'mountpoint',
                    '') == '/boot/efi' and partition['filesystem'] == 'vfat':
                partition_data[
                    'bootefi'] = self.photon_root + partition['mountpoint']
                partition_data['bootefidirectory'] = partition['mountpoint']

        sysroot_ostree = os.path.join(self.photon_root, "ostree")
        loader0 = os.path.join(partition_data['boot'], "loader.0")
        loader1 = os.path.join(partition_data['boot'], "loader.1")

        boot0 = os.path.join(sysroot_ostree, "boot.0")
        boot1 = os.path.join(sysroot_ostree, "boot.1")

        boot01 = os.path.join(sysroot_ostree, "boot.0.1")
        boot11 = os.path.join(sysroot_ostree, "boot.1.1")

        self.get_ostree_repo_url()

        bootmode = self.install_config['bootmode']
        if self.default_repo:
            self.run([['mkdir', '-p', '{}/repo'.format(self.photon_root)]])
            if self.install_config['ui']:
                self.progress_bar.show_loading("Unpacking local OSTree repo")
            ostree_repo_tree = "/mnt/media/ostree-repo.tar.gz"
            if not os.path.isfile(ostree_repo_tree):
                ostree_repo_tree = os.path.abspath(os.getcwd() +
                                                   "/../ostree-repo.tar.gz")
            self.run([[
                'tar', '--warning=none', '-xf', '{}'.format(ostree_repo_tree),
                '-C'
                '{}/repo'.format(self.photon_root)
            ]])
            self.local_repo_path = "{}/repo".format(self.photon_root)
            self.ostree_repo_url = self.repo_config['OSTREEREPOURL']
            self.ostree_ref = self.repo_config['OSTREEREFS']
            if self.install_config['ui']:
                self.progress_bar.update_loading_message("Unpacking done")

        self.deploy_ostree(self.ostree_repo_url, self.ostree_ref)

        commit_number = self.get_commit_number(self.ostree_ref)
        self.do_systemd_tmpfiles_commands(commit_number)

        self.mount_devices_in_deployment(commit_number)
        deployment = os.path.join(
            self.photon_root,
            "ostree/deploy/photon/deploy/" + commit_number + ".0/")
        self.create_symlink_directory(deployment)

        deployment_sysroot = os.path.join(deployment, "sysroot")
        deployment_boot = os.path.join(deployment, "boot")
        if bootmode == 'dualboot' or bootmode == 'efi':
            deployment_bootefi = os.path.join(deployment, "boot/efi")

        if os.path.exists(loader1):
            cmd = []
            cmd.append(['mv', '{}'.format(loader1), '{}'.format(loader0)])
            cmd.append(['mv', '{}'.format(boot1), '{}'.format(boot0)])
            cmd.append(['mv', '{}'.format(boot11), '{}'.format(boot01)])
            self.run(cmd)

        mount_bind = []
        mount_bind.append([
            'mount', '--bind', '{}'.format(self.photon_root),
            '{}'.format(deployment_sysroot)
        ])
        mount_bind.append([
            'mount', '--bind', '{}'.format(partition_data['boot']),
            '{}'.format(deployment_boot)
        ])
        if bootmode == 'dualboot' or bootmode == 'efi':
            mount_bind.append([
                'mount', '--bind', '{}'.format(partition_data['bootefi']),
                '{}'.format(deployment_bootefi)
            ])
        self.run(mount_bind)

        if bootmode == 'dualboot' or bootmode == 'bios':
            self.run([[
                'chroot', '{}'.format(deployment), 'bash', '-c',
                'grub2-install --target=i386-pc --force --boot-directory={} {};'
                .format(partition_data['bootdirectory'],
                        self.install_config['disk'])
            ]], "Generating Grub binaries for BIOS mode")
        if bootmode == 'dualboot' or bootmode == 'efi':
            self.run(
                [['mkdir', '-p', partition_data['bootefi'] + '/boot/grub2']],
                "Generating grub.cfg for efi boot")
            with open(
                    os.path.join(partition_data['bootefi'],
                                 'boot/grub2/grub.cfg'), "w") as grub_cfg:
                grub_cfg.write("search -n -u {} -s\n".format(
                    self._get_uuid(
                        self.install_config['partitions_data']['boot'])))
                grub_cfg.write("configfile {}grub2/grub.cfg\n".format(
                    self.install_config['partitions_data']['bootdirectory']))
            self.run([[
                'chroot', '{}'.format(deployment), 'bash', '-c',
                'mkdir -p /boot/grub2;'
            ]])
        self.run([[
            'chroot', '{}'.format(deployment), 'bash', '-c',
            'grub2-mkconfig -o {}/grub2/grub.cfg;'.format(
                partition_data['bootdirectory'])
        ]])

        if bootmode == 'dualboot' or bootmode == 'efi':
            setup_efi = []
            setup_efi.append([
                'chroot', '{}'.format(deployment), 'bash', '-c',
                'cp -rpf /usr/lib/ostree-boot/grub2/* {}/boot/grub2/;'.format(
                    partition_data['bootefidirectory'])
            ])
            setup_efi.append([
                'chroot', '{}'.format(deployment), 'bash', '-c',
                'cp -rpf /usr/lib/ostree-boot/efi/* {};'.format(
                    partition_data['bootefidirectory'])
            ])
            self.run(setup_efi, "Setup efi partition")

        setup_boot = []
        setup_boot.append([
            'chroot', '{}'.format(deployment), 'bash', '-c',
            'rm -rf {}/grub2/fonts;'.format(partition_data['bootdirectory'])
        ])
        setup_boot.append([
            'chroot', '{}'.format(deployment), 'bash', '-c',
            'ln -sf /usr/lib/ostree-boot/grub2/* {}/grub2/;'.format(
                partition_data['bootdirectory'])
        ])
        self.run(setup_boot, "Setup boot partition")

        if os.path.exists(loader0):
            cmd = []
            cmd.append(['mv', '{}'.format(loader0), '{}'.format(loader1)])
            cmd.append(['mv', '{}'.format(boot0), '{}'.format(boot1)])
            cmd.append(['mv', '{}'.format(boot01), '{}'.format(boot11)])
            self.run(cmd)

        partuuid = self._get_partuuid(
            self.install_config['partitions_data']['root'])
        if partuuid == "":
            self.run([[
                'chroot', '{}'.format(deployment), 'bash', '-c',
                "ostree admin instutil set-kargs '$photon_cmdline' '$systemd_cmdline' root={};"
                .format(self.install_config['partitions_data']['root'])
            ]], "Add ostree  menu entry in grub.cfg")
        else:
            self.run([[
                'chroot', '{}'.format(deployment), 'bash', '-c',
                "ostree admin instutil set-kargs '$photon_cmdline' '$systemd_cmdline' root=PARTUUID={};"
                .format(partuuid)
            ]], "Add ostree  menu entry in grub.cfg")

        sysroot_grub2_grub_cfg = os.path.join(self.photon_root,
                                              "boot/grub2/grub.cfg")
        self.run([[
            'ln', '-sf', '../loader/grub.cfg',
            '{}'.format(sysroot_grub2_grub_cfg)
        ]])
        if os.path.exists(loader1):
            cmd = []
            cmd.append(['mv', '{}'.format(loader1), '{}'.format(loader0)])
            cmd.append(['mv', '{}'.format(boot1), '{}'.format(boot0)])
            cmd.append(['mv', '{}'.format(boot11), '{}'.format(boot01)])
            self.run(cmd)

        deployment_fstab = os.path.join(deployment, "etc/fstab")
        self._create_fstab(deployment_fstab)

        self.run([[
            'mount', '--bind', '{}'.format(deployment), '{}'.format(
                self.photon_root)
        ]], "Bind deployment to photon root")

    def run(self, commands, comment=None):
        if comment != None:
            self.logger.info("Installer: {} ".format(comment))
            if self.install_config['ui']:
                self.progress_bar.update_loading_message(comment)

        for command in commands:
            retval = self.cmd.run(command)
            if retval != 0 and "systemd-tmpfiles" not in command:
                self.logger.error(
                    "Installer: failed in {} with error code {}".format(
                        command, retval))
                self.exit_gracefully()
        return retval
Esempio n. 2
0
class Installer(object):
    """
    Photon installer
    """
    mount_command = os.path.dirname(__file__) + "/mk-mount-disk.sh"
    finalize_command = "./mk-finalize-system.sh"
    chroot_command = os.path.dirname(__file__) + "/mk-run-chroot.sh"
    unmount_disk_command = os.path.dirname(__file__) + "/mk-unmount-disk.sh"
    default_partitions = [{"mountpoint": "/", "size": 0, "filesystem": "ext4"}]

    def __init__(self,
                 install_config,
                 maxy=0,
                 maxx=0,
                 iso_installer=False,
                 rpm_path=os.path.dirname(__file__) + "/../stage/RPMS",
                 log_path=os.path.dirname(__file__) + "/../stage/LOGS",
                 log_level="info"):
        self.exiting = False
        self.install_config = install_config
        self.install_config['iso_installer'] = iso_installer
        self.rpm_path = rpm_path
        self.logger = Logger.get_logger(log_path, log_level, not iso_installer)
        self.cmd = CommandUtils(self.logger)

        if 'working_directory' in self.install_config:
            self.working_directory = self.install_config['working_directory']
        else:
            self.working_directory = "/mnt/photon-root"

        self.cmd.run(['mkdir', '-p', self.working_directory])

        if 'prepare_script' in self.install_config:
            self.prepare_command = self.install_config['prepare_script']
        else:
            self.prepare_command = os.path.dirname(
                __file__) + "/mk-prepare-system.sh"

        self.photon_root = self.working_directory + "/photon-chroot"
        self.installer_path = os.path.dirname(os.path.abspath(__file__))
        self.tdnf_conf_path = self.working_directory + "/tdnf.conf"
        self.tdnf_repo_path = self.working_directory + "/photon-local.repo"
        self.rpm_cache_dir = self.photon_root + '/cache/tdnf/photon-local/rpms'
        # used by tdnf.conf as cachedir=, tdnf will append the rest
        self.rpm_cache_dir_short = self.photon_root + '/cache/tdnf'

        if 'setup_grub_script' in self.install_config:
            self.setup_grub_command = self.install_config['setup_grub_script']
        else:
            self.setup_grub_command = os.path.dirname(
                __file__) + "/mk-setup-grub.sh"
        self.rpms_tobeinstalled = None

        if self.install_config['iso_installer']:
            #initializing windows
            height = 10
            width = 75
            progress_padding = 5

            progress_width = width - progress_padding
            starty = (maxy - height) // 2
            startx = (maxx - width) // 2
            self.window = Window(height, width, maxy, maxx,
                                 'Installing Photon', False)
            self.progress_bar = ProgressBar(starty + 3,
                                            startx + progress_padding // 2,
                                            progress_width)

        signal.signal(signal.SIGINT, self.exit_gracefully)

    def install(self):
        """
        Install photon system and handle exception
        """
        if self.install_config['iso_installer']:
            self.window.show_window()
            self.progress_bar.initialize('Initializing installation...')
            self.progress_bar.show()

        try:
            return self._unsafe_install()
        except Exception as inst:
            if self.install_config['iso_installer']:
                self.logger.exception(repr(inst))
                self.exit_gracefully()
            else:
                raise

    def _unsafe_install(self):
        """
        Install photon system
        """
        self._format_disk()
        self._setup_install_repo()
        self._initialize_system()
        self._install_packages()
        self._enable_network_in_chroot()
        self._finalize_system()
        self._cleanup_install_repo()
        self._execute_modules(modules.commons.POST_INSTALL)
        self._post_install()
        self._disable_network_in_chroot()
        self._cleanup_and_exit()
        return ActionResult(True, None)

    def exit_gracefully(self, signal1=None, frame1=None):
        """
        This will be called if the installer interrupted by Ctrl+C, exception
        or other failures
        """
        del signal1
        del frame1
        if not self.exiting:
            self.exiting = True
            if self.install_config['iso_installer']:
                self.progress_bar.hide()
                self.window.addstr(
                    0, 0, 'Oops, Installer got interrupted.\n\n' +
                    'Press any key to get to the bash...')
                self.window.content_window().getch()

            self._cleanup_install_repo()
            self._cleanup_and_exit()
        sys.exit(1)

    def _cleanup_and_exit(self):
        """
        Unmount the disk, eject cd and exit
        """
        command = [Installer.unmount_disk_command, '-w', self.photon_root]
        command.extend(self._generate_partitions_param(reverse=True))
        retval = self.cmd.run(command)
        if retval != 0:
            self.logger.error("Failed to unmount disks")
        if self.install_config['iso_installer']:
            self.progress_bar.hide()
            self.window.addstr(
                0, 0,
                'Congratulations, Photon has been installed in {0} secs.\n\n'
                'Press any key to continue to boot...'.format(
                    self.progress_bar.time_elapsed))
            if 'ui_install' in self.install_config:
                self.window.content_window().getch()
            self._eject_cdrom()

    def _create_installrpms_list(self):
        """
        Prepare RPM list and copy rpms
        """
        # prepare the RPMs list
        json_pkg_to_rpm_map = JsonWrapper(
            self.install_config["pkg_to_rpm_map_file"])
        pkg_to_rpm_map = json_pkg_to_rpm_map.read()

        self.rpms_tobeinstalled = []
        selected_packages = self.install_config['packages']

        for pkg in selected_packages:
            versionindex = pkg.rfind("-")
            if versionindex == -1:
                raise Exception("Invalid pkg name: " + pkg)
            package = pkg[:versionindex]
            if pkg in pkg_to_rpm_map:
                if pkg_to_rpm_map[pkg]['rpm'] is not None:
                    name = pkg_to_rpm_map[pkg]['rpm']
                    basename = os.path.basename(name)
                    self.rpms_tobeinstalled.append({
                        'filename': basename,
                        'path': name,
                        'package': package
                    })

    def _copy_files(self):
        """
        Copy the rpm files and instal scripts.
        """
        # Make the photon_root directory if not exits
        retval = self.cmd.run(['mkdir', '-p', self.photon_root])
        if retval != 0:
            self.logger.error("Fail to create the root directory")
            self.exit_gracefully()

        # Copy the installer files
        retval = self.cmd.run(
            ['cp', '-r',
             os.path.dirname(__file__), self.photon_root])
        if retval != 0:
            self.logger.error("Fail to copy install scripts")
            self.exit_gracefully()

        self._create_installrpms_list()

    def _bind_installer(self):
        """
        Make the photon_root/installer directory if not exits
        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,
        the file can still be accessed as /installer/mk-finalize-system.sh.
        """
        # Make the photon_root/installer directory if not exits
        if (self.cmd.run(
            ['mkdir', '-p',
             os.path.join(self.photon_root, "installer")]) != 0
                or self.cmd.run([
                    'mount', '--bind', self.installer_path,
                    os.path.join(self.photon_root, "installer")
                ]) != 0):
            self.logger.error("Fail to bind installer")
            self.exit_gracefully()

    def _unbind_installer(self):
        # unmount the installer directory
        retval = self.cmd.run(
            ['umount', os.path.join(self.photon_root, "installer")])
        if retval != 0:
            self.logger.error("Fail to unbind the installer directory")
        # remove the installer directory
        retval = self.cmd.run(
            ['rm', '-rf',
             os.path.join(self.photon_root, "installer")])
        if retval != 0:
            self.logger.error("Fail to remove the installer directory")

    def _bind_repo_dir(self):
        """
        Bind repo dir for tdnf installation
        """
        if self.rpm_path.startswith("https://") or self.rpm_path.startswith(
                "http://"):
            return
        if (self.cmd.run(['mkdir', '-p', self.rpm_cache_dir]) != 0
                or self.cmd.run([
                    'mount', '--bind', self.rpm_path, self.rpm_cache_dir
                ]) != 0):
            self.logger.error("Fail to bind cache rpms")
            self.exit_gracefully()

    def _unbind_repo_dir(self):
        """
        Unbind repo dir after installation
        """
        if self.rpm_path.startswith("https://") or self.rpm_path.startswith(
                "http://"):
            return
        if (self.cmd.run(['umount', self.rpm_cache_dir]) != 0
                or self.cmd.run(['rm', '-rf', self.rpm_cache_dir]) != 0):
            self.logger.error("Fail to unbind cache rpms")

    def _update_fstab(self):
        """
        update fstab
        """
        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")

            for partition in self.install_config['disk']['partitions']:
                options = 'defaults'
                dump = 1
                fsck = 2

                if 'mountpoint' in partition and partition['mountpoint'] == '/':
                    options = options + ',barrier,noatime,noacl,data=ordered'
                    fsck = 1

                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")

    def _generate_partitions_param(self, reverse=False):
        """
        Generate partition param for mount command
        """
        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

    def _initialize_system(self):
        """
        Prepare the system to install photon
        """
        #Setup the disk
        command = [Installer.mount_command, '-w', self.photon_root]
        command.extend(self._generate_partitions_param())
        retval = self.cmd.run(command)
        if retval != 0:
            self.logger.info("Failed to setup the disk for installation")
            self.exit_gracefully()

        if self.install_config['iso_installer']:
            self.progress_bar.update_message('Initializing system...')
            self._bind_installer()
            self._bind_repo_dir()
            retval = self.cmd.run(
                [self.prepare_command, '-w', self.photon_root, 'install'])
            if retval != 0:
                self.logger.info(
                    "Failed to bind the installer and repo needed by tdnf")
                self.exit_gracefully()
        else:
            self._copy_files()
            #Setup the filesystem basics
            retval = self.cmd.run(
                [self.prepare_command, '-w', self.photon_root, self.rpm_path])
            if retval != 0:
                self.logger.info("Failed to setup the file systems basics")
                self.exit_gracefully()

    def _finalize_system(self):
        """
        Finalize the system after the installation
        """
        #Setup the disk
        retval = self.cmd.run([
            Installer.chroot_command, '-w', self.photon_root,
            Installer.finalize_command, '-w', self.photon_root
        ])
        if retval != 0:
            self.logger.error(
                "Fail to setup the target system after the installation")

    def _cleanup_install_repo(self):
        if self.install_config['iso_installer']:
            self._unbind_installer()
            self._unbind_repo_dir()
            # Disable the swap file
            retval = self.cmd.run(
                ['swapoff', self.photon_root + '/cache/swapfile'])
            if retval != 0:
                self.logger.error("Fail to swapoff")
            # remove the tdnf cache directory and the swapfile.
            retval = self.cmd.run(
                ['rm', '-rf',
                 os.path.join(self.photon_root, "cache")])
            if retval != 0:
                self.logger.error("Fail to remove the cache")

    def _post_install(self):
        # install grub
        if 'boot_partition_number' not in self.install_config['disk']:
            self.install_config['disk']['boot_partition_number'] = 1

        retval = self.cmd.run([
            self.setup_grub_command, '-w', self.photon_root,
            self.install_config.get('boot', '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'])
        ])

        if retval != 0:
            raise Exception("Bootloader (grub2) setup failed")

        self._update_fstab()
        if not self.install_config['iso_installer']:
            retval = self.cmd.run(
                ['rm', '-rf',
                 os.path.join(self.photon_root, "installer")])
            if retval != 0:
                self.logger.error("Fail to remove the installer directory")

    def _execute_modules(self, phase):
        """
        Execute the scripts in the modules folder
        """
        sys.path.append(
            os.path.abspath(os.path.join(os.path.dirname(__file__),
                                         "modules")))
        modules_paths = glob.glob(
            os.path.abspath(os.path.join(os.path.dirname(__file__), 'modules'))
            + '/m_*.py')
        for mod_path in modules_paths:
            module = os.path.splitext(os.path.basename(mod_path))[0]
            try:
                __import__(module)
                mod = sys.modules[module]
            except ImportError:
                self.logger.error('Error importing module {}'.format(module))
                continue

            # the module default is disabled
            if not hasattr(mod, 'enabled') or mod.enabled is False:
                self.logger.info("module {} is not enabled".format(module))
                continue
            # check for the install phase
            if not hasattr(mod, 'install_phase'):
                self.logger.error(
                    "Error: can not defind module {} phase".format(module))
                continue
            if mod.install_phase != phase:
                self.logger.info("Skipping module {0} for phase {1}".format(
                    module, phase))
                continue
            if not hasattr(mod, 'execute'):
                self.logger.error(
                    "Error: not able to execute module {}".format(module))
                continue
            self.logger.info("Executing: " + module)
            mod.execute(self)

    def _adjust_packages_for_vmware_virt(self):
        """
        Install linux_esx on Vmware virtual machine if requested
        """
        try:
            if self.install_config['install_linux_esx']:
                regex = re.compile(r'^linux-[0-9]|^initramfs-[0-9]')
                self.install_config['packages'] = [
                    x for x in self.install_config['packages']
                    if not regex.search(x)
                ]
                self.install_config['packages'].append('linux-esx')
        except KeyError:
            pass

    def _format_disk(self):
        """
        Partition and format the disk
        """
        # skip partitioning if installer was called from image
        if not self.install_config['iso_installer']:
            return

        self.progress_bar.update_message('Partitioning...')

        if 'partitions' in self.install_config:
            partitions = self.install_config['partitions']
        else:
            partitions = Installer.default_partitions

        # do partitioning
        partitions_data = self.partition_disk(self.install_config['disk'],
                                              partitions)

        if partitions_data == None:
            raise Exception("Partitioning failed.")
        self.install_config['disk'] = partitions_data

    def _setup_install_repo(self):
        """
        Setup the tdnf repo for installation
        """
        if self.install_config['iso_installer']:
            keepcache = False
            with open(self.tdnf_repo_path, "w") as repo_file:
                repo_file.write("[photon-local]\n")
                repo_file.write("name=VMWare Photon installer repo\n")
                if self.rpm_path.startswith(
                        "https://") or self.rpm_path.startswith("http://"):
                    repo_file.write("baseurl={}\n".format(
                        self.rpm_path.replace('/', r'\/')))
                else:
                    repo_file.write("baseurl=file://{}\n".format(
                        self.rpm_cache_dir))
                    keepcache = True
                repo_file.write("gpgcheck=0\nenabled=1\n")
            with open(self.tdnf_conf_path, "w") as conf_file:
                conf_file.writelines([
                    "[main]\n", "gpgcheck=0\n", "installonly_limit=3\n",
                    "clean_requirements_on_remove=true\n"
                ])
                # baseurl and cachedir are bindmounted to rpm_path, we do not
                # want input RPMS to be removed after installation.
                if keepcache:
                    conf_file.write("keepcache=1\n")
                conf_file.write("repodir={}\n".format(self.working_directory))
                conf_file.write("cachedir={}\n".format(
                    self.rpm_cache_dir_short))

    def _install_packages(self):
        """
        Install packages using tdnf or rpm command
        """
        if self.install_config['iso_installer']:
            self._tdnf_install_packages()
        else:
            self._rpm_install_packages()

    def _tdnf_install_packages(self):
        """
        Install packages using tdnf command
        """
        self._adjust_packages_for_vmware_virt()
        selected_packages = self.install_config['packages']
        state = 0
        packages_to_install = {}
        total_size = 0
        self.logger.debug("tdnf install --installroot {0} {1} -c {2}".format(
            self.photon_root, " ".join(selected_packages),
            self.tdnf_conf_path))
        process = subprocess.Popen(['tdnf', 'install'] + selected_packages + [
            '--installroot', self.photon_root, '--assumeyes', '-c',
            self.tdnf_conf_path
        ],
                                   stdout=subprocess.PIPE,
                                   stderr=subprocess.PIPE)
        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:
                self.logger.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:
            self.logger.error("Failed to install some packages")
            self.logger.error(process.communicate()[1].decode())
            self.exit_gracefully()
        self.progress_bar.show_loading('Finalizing installation')

    def _rpm_install_packages(self):
        """
        Install packages using rpm command
        """
        rpms = []
        for rpm in self.rpms_tobeinstalled:
            # We already installed the filesystem in the preparation
            if rpm['package'] == 'filesystem':
                continue
            rpms.append(rpm['filename'])
        rpms = set(rpms)
        rpm_paths = []
        for root, _, files in os.walk(self.rpm_path):
            for file in files:
                if file in rpms:
                    rpm_paths.append(os.path.join(root, file))

        # --nodeps is for hosts which do not support rich dependencies
        rpm_params = [
            '--nodeps', '--root', self.photon_root, '--dbpath', '/var/lib/rpm'
        ]

        if ('type' in self.install_config
                and (self.install_config['type'] in ['micro', 'minimal'])):
            rpm_params.append('--excludedocs')

        self.logger.info("installing packages {0}, with params {1}".format(
            rpm_paths, rpm_params))
        retval = self.cmd.run(['rpm', '-Uvh'] + rpm_params + rpm_paths)
        if retval != 0:
            self.exit_gracefully()

    def _eject_cdrom(self):
        """
        Eject the cdrom on request
        """
        eject_cdrom = True
        if 'eject_cdrom' in self.install_config and not self.install_config[
                'eject_cdrom']:
            eject_cdrom = False
        if eject_cdrom:
            self.cmd.run(['eject', '-r'])

    def _enable_network_in_chroot(self):
        """
        Enable network in chroot
        """
        if os.path.exists("/etc/resolv.conf"):
            shutil.copy("/etc/resolv.conf", self.photon_root + '/etc/.')

    def _disable_network_in_chroot(self):
        """
        disable network in chroot
        """
        if os.path.exists(self.photon_root + '/etc/resolv.conf'):
            os.remove(self.photon_root + '/etc/resolv.conf')

    def partition_compare(self, p):
        if 'mountpoint' in p:
            return (1, len(p['mountpoint']), p['mountpoint'])
        return (0, 0, "A")

    def partition_disk(self, disk, partitions):
        partitions_data = {}
        partitions_data['disk'] = disk
        partitions_data['partitions'] = partitions

        # Clear the disk
        retval = self.cmd.run(['sgdisk', '-o', '-g', disk])
        if retval != 0:
            self.logger.error("Failed clearing disk {0}".format(disk))
            return None
        # Partitioning the disk
        extensible_partition = None
        partitions_count = len(partitions)
        partition_number = 3
        # Add part size and grub flags

        bios_flag = ':ef02'
        part_size = '+2M'
        # Adding the bios partition
        partition_cmd = ['sgdisk', '-n 1::' + part_size]

        efi_flag = ':ef00'
        part_size = '+3M'
        # Adding the efi partition
        partition_cmd.extend(['-n 2::' + part_size])
        # Adding the known size partitions

        arch = subprocess.check_output(['uname', '-m'],
                                       universal_newlines=True)
        if "x86" not in arch:
            partition_number = 2
            # Adding the efi partition
            partition_cmd = ['sgdisk', '-n 1::' + part_size]

        for partition in partitions:
            if partition['size'] == 0:
                # Can not have more than 1 extensible partition
                if extensible_partition != None:
                    self.logger.error(
                        "Can not have more than 1 extensible partition")
                    return None
                extensible_partition = partition
            else:
                partition_cmd.extend([
                    '-n', '{}::+{}M'.format(partition_number,
                                            partition['size'])
                ])

            partition['partition_number'] = partition_number
            prefix = ''
            if 'nvme' in disk or 'mmcblk' in disk:
                prefix = 'p'
            partition['path'] = disk + prefix + repr(partition_number)
            partition_number = partition_number + 1

        # Adding the last extendible partition
        if extensible_partition:
            partition_cmd.extend(
                ['-n', repr(extensible_partition['partition_number'])])

        partition_cmd.extend(['-p', disk])

        # Run the partitioning command
        retval = self.cmd.run(partition_cmd)
        if retval != 0:
            self.logger.error(
                "Failed partition disk, command: {0}".format(partition_cmd))
            return None

        if "x86" not in arch:
            retval = self.cmd.run(['sgdisk', '-t1' + efi_flag, disk])
            if retval != 0:
                self.logger.error("Failed to setup efi partition")
                return None

        else:
            retval = self.cmd.run(['sgdisk', '-t1' + bios_flag, disk])
            if retval != 0:
                self.logger.error("Failed to setup bios partition")
                return None

            retval = self.cmd.run(['sgdisk', '-t2' + efi_flag, disk])
            if retval != 0:
                self.logger.error("Failed to setup efi partition")
                return None
        # Format the filesystem
        for partition in partitions:
            if "mountpoint" in partition:
                if partition['mountpoint'] == '/':
                    partitions_data['root'] = partition['path']
                    partitions_data['root_partition_number'] = partition[
                        'partition_number']
                elif partition['mountpoint'] == '/boot':
                    partitions_data['boot'] = partition['path']
                    partitions_data['boot_partition_number'] = partition[
                        'partition_number']
                    partitions_data['bootdirectory'] = '/'
            if partition['filesystem'] == "swap":
                retval = self.cmd.run(['mkswap', partition['path']])
                if retval != 0:
                    self.logger.error(
                        "Failed to create swap partition @ {}".format(
                            partition['path']))
                    return None
            else:
                mkfs_cmd = [
                    'mkfs', '-t', partition['filesystem'], partition['path']
                ]
                retval = self.cmd.run(mkfs_cmd)
                if retval != 0:
                    self.logger.error(
                        "Failed to format {} partition @ {}".format(
                            partition['filesystem'], partition['path']))
                    return None

        # Check if there is no root partition
        if 'root' not in partitions_data:
            self.logger.error("There is no partition assigned to root '/'")
            return None

        if 'boot' not in partitions_data:
            partitions_data['boot'] = partitions_data['root']
            partitions_data['boot_partition_number'] = partitions_data[
                'root_partition_number']
            partitions_data['bootdirectory'] = '/boot/'

        partitions.sort(key=lambda p: self.partition_compare(p))

        return partitions_data