class UpdateMaker():
    """A class for making updates for OreSat Linux Updater daemon"""

    def __init__(self, board: str):
        """
        Parameters
        ----------
        board: str
            The board to make the update for.
        """
        self._board = board
        self._status_file = ""
        self._board = board
        self._cache = Cache(rootdir=ROOT_DIR)
        self._cache.update(raise_on_error=False)
        self._cache.open()
        self._deb_pkgs = []
        self._inst_list = []

        # make sure all dir exist
        Path(OLU_DIR).mkdir(parents=True, exist_ok=True)
        Path(ROOT_DIR).mkdir(parents=True, exist_ok=True)
        Path(DOWNLOAD_DIR).mkdir(parents=True, exist_ok=True)
        Path(UPDATE_CACHE_DIR).mkdir(parents=True, exist_ok=True)
        Path(STATUS_CACHE_DIR).mkdir(parents=True, exist_ok=True)

        # clear download dir
        for i in listdir(DOWNLOAD_DIR):
            if i.endswith(".deb"):
                remove(DOWNLOAD_DIR + i)

        status_files = []
        for i in listdir(STATUS_CACHE_DIR):
            status_files.append(OLMFile(load=i))
        status_files.sort()

        # find latest olu status tar file
        for i in status_files:
            if i.name == board:
                self._status_file = STATUS_CACHE_DIR + i.name
                break

        if self._status_file == "":
            msg = "No status file for {} board in cache".format(board)
            raise FileNotFoundError(msg)

        # update status file
        dpkg_data = read_dpkg_status_file(self._status_file)
        with open(DPKG_STATUS_FILE) as fptr:
            fptr.write(dpkg_data)

        # TODO deal with update files that are not installed yet.

    def add_packages(self, packages: list):
        """Add deb packages to be installed.

        Parameters
        ----------
        packages: list
            A list of deb packages to install on the board.
        """

        if packages == []:
            raise ValueError("Requires a list of packages to install")

        inst_deb_pkgs = []

        for pkg in packages:
            pkg_obj = self._cache[pkg]
            pkg_obj.mark_install()  # this will mark all dependencies too

            # find new packages (dependencies) that are marked
            for deb_pkg in self._cache:
                if deb_pkg.marked_install and \
                        deb_pkg.name not in self._deb_pkgs:
                    self._deb_pkgs.append(deb_pkg.name)
                    inst_deb_pkgs.append(deb_pkg.name)

        new_inst = Instruction(InstructionType.DPKG_INSTALL, inst_deb_pkgs)
        self._inst_list.append(new_inst)

    def remove_packages(self, packages: list):
        """Remove deb packages on board.

        Parameters
        ----------
        packages: list
            A list of deb packages to remove on the board.
        """

        if packages == []:
            raise ValueError("Requires a list of packages to remove")

        new_inst = Instruction(InstructionType.DPKG_REMOVE, packages)
        self._inst_list.append(new_inst)

    def purge_packages(self, packages: list):
        """Purge deb packages on board.

        Parameters
        ----------
        packages: list
            A list of deb packages to remove on the board.
        """

        if packages == []:
            raise ValueError("Requires a list of packages to remove")

        new_inst = Instruction(InstructionType.DPKG_PURGE, packages)
        self._inst_list.append(new_inst)

    def add_bash_scripts(self, bash_scipts: list):
        """Run bash scripts on the board.

        Parameters
        ----------
        bash_scipts: list
            A list of bash script to run on the board.
        """

        if bash_scipts == []:
            raise ValueError("Requires a list of bash scipts to run")

        new_inst = Instruction(InstructionType.BASH_SCRIPT, bash_scipts)
        self._inst_list.append(new_inst)

    def add_support_files(self, support_files: list):
        """Add a support files to update archive.

        Parameters
        ----------
        support_files: list
            A list of support files to add to the update.
        """

        for s_file in support_files:
            if isfile(s_file):
                raise ValueError(" {} was not found".format(s_file))

        new_inst = Instruction(InstructionType.SUPPORT_FILE, support_files)
        self._inst_list.append(new_inst)

    def status(self):
        """Print the contexts of instructions list"""

        for i in self._inst_list:
            print(i)

    def make_update_archive(self):
        """Make the update archive"""

        # download deb files
        self._cache.fetch_archives()

        # replace package name with deb filepath in instruction obj
        for inst in self._inst_list:
            if not inst.type == InstructionType.DPKG_INSTALL:
                continue

            for i in range(len(inst.items)):
                found = False
                for deb_file in listdir(DOWNLOAD_DIR):
                    if not deb_file.endswith(".deb"):
                        continue

                    if deb_file.startswith(inst.items[i]+"_"):
                        inst.items[i] = DOWNLOAD_DIR + deb_file
                        found = True
                        break

                if found is True:
                    break

        print("Making tar")

        update_file = create_update_file(self._board, self._inst_list, "./")

        print("{} was made".format(update_file))
Ejemplo n.º 2
0
class UpdateMaker():
    """A class for making updates for OreSat Linux Updater daemon"""
    def __init__(self, board: str):
        """
        Parameters
        ----------
        board: str
            The board to make the update for.
        """
        self._board = board
        self._status_file = ""
        self._board = board
        self._cache = Cache(rootdir=ROOT_DIR)
        self._deb_pkgs = []
        self._inst_list = []
        self._not_installed_yet_list = []
        self._not_removed_yet_list = []

        print("updating cache")
        self._cache.update(raise_on_error=False)
        self._cache.open()

        # copying the context of the real root apt source.list file into the local one
        if stat(OLU_APT_SOURCES_FILE
                ).st_size == 0 or not isfile(OLU_APT_SOURCES_FILE):
            copyfile(SYSTEM_APT_SOURCES_FILE, OLU_APT_SOURCES_FILE)

            # adding OreSat Debian apt repo
            with open(OLU_APT_SOURCES_FILE, "a") as f:
                f.write(
                    "deb [trusted=yes] https://debian.oresat.org/packages ./")

        # copying the apt repo signatures
        if len(listdir(OLU_SIGNATURES_DIR)) == 3:
            for root, dirs, files in walk(SYSTEM_SIGNATURES_DIR):
                for file in files:
                    if file != "lock":
                        copyfile(SYSTEM_SIGNATURES_DIR + file,
                                 OLU_SIGNATURES_DIR + file)

        # clear download dir
        for i in listdir(DOWNLOAD_DIR):
            if i.endswith(".deb"):
                remove(DOWNLOAD_DIR + i)

        status_files = []
        for i in listdir(STATUS_CACHE_DIR):
            status_files.append(OLMFile(load=i))
        status_files.sort()

        # find latest olu status tar file
        for i in status_files:
            if i.board == board:
                self._status_file = STATUS_CACHE_DIR + i.name
                break

        if self._status_file == "":
            msg = "No status file for {} board in cache".format(board)
            raise FileNotFoundError(msg)

        # update status file
        dpkg_data = read_dpkg_status_file(self._status_file)
        with open(DPKG_STATUS_FILE, "w") as fptr:
            fptr.write(dpkg_data)

        # dealing with update files that are not installed yet
        olu_status_data = read_olu_status_file(self._status_file)
        for file in literal_eval(olu_status_data):
            with tarfile.open(UPDATE_CACHE_DIR + file, "r") as tar:
                with tar.extractfile("instructions.txt") as instructions:
                    for i in json.loads(instructions.read()):
                        if i["type"] == "DPKG_INSTALL":
                            for pkg in i["items"]:
                                pkg_obj = self._cache[pkg.split('_')[0]]
                                pkg_obj.mark_install()
                            self._not_installed_yet_list.extend(i["items"])
                        elif i["type"] == "DPKG_REMOVE" or i[
                                "type"] == "DPKG_PURGE":
                            self._not_removed_yet_list.extend(i["items"])

    @property
    def not_installed_yet(self) -> list:
        return [pkg.split('_')[0] for pkg in self._not_installed_yet_list]

    @property
    def not_removed_yet(self) -> list:
        return [pkg.split('_')[0] for pkg in self._not_removed_yet_list]

    def add_packages(self, packages: list, reinstall_not_installed: list,
                     reinstall_not_removed: list):
        """Add deb packages to be installed.

        Parameters
        ----------
        packages: list
            A list of deb packages to install on the board.
        """

        if packages == []:
            raise ValueError("Requires a list of packages to install")

        inst_deb_pkgs = []

        for pkg in packages:
            pkg_obj = self._cache[pkg]

            # checking the not yet installed and removed packages
            if pkg_obj.name in reinstall_not_removed:
                pkg_index = self.not_removed_yet.index(pkg_obj.name)
                self._not_removed_yet_list.pop(pkg_index)
                pkg_obj.mark_install()
            elif pkg_obj.name in reinstall_not_installed:
                pkg_index = self.not_installed_yet.index(pkg_obj.name)
                self._not_installed_yet_list.pop(pkg_index)
                pkg_obj.mark_install()
            elif pkg_obj.name not in self.not_installed_yet and pkg_obj.name not in self.not_removed_yet:
                pkg_obj.mark_install()

            # find new packages (dependencies) that are marked
            for deb_pkg in self._cache:
                if deb_pkg.marked_install and \
                        deb_pkg.name not in self._deb_pkgs:
                    self._deb_pkgs.append(deb_pkg.name)
                    inst_deb_pkgs.append(deb_pkg.name)

        new_inst = Instruction(InstructionType.DPKG_INSTALL, inst_deb_pkgs)
        self._inst_list.append(new_inst)

    def remove_packages(self, packages: list):
        """Remove deb packages on board.

        Parameters
        ----------
        packages: list
            A list of deb packages to remove on the board.
        """

        if packages == []:
            raise ValueError("Requires a list of packages to remove")

        new_inst = Instruction(InstructionType.DPKG_REMOVE, packages)
        self._inst_list.append(new_inst)

    def purge_packages(self, packages: list):
        """Purge deb packages on board.

        Parameters
        ----------
        packages: list
            A list of deb packages to remove on the board.
        """

        if packages == []:
            raise ValueError("Requires a list of packages to remove")

        new_inst = Instruction(InstructionType.DPKG_PURGE, packages)
        self._inst_list.append(new_inst)

    def add_bash_scripts(self, bash_scipts: list):
        """Run bash scripts on the board.

        Parameters
        ----------
        bash_scipts: list
            A list of bash script to run on the board.
        """

        if bash_scipts == []:
            raise ValueError("Requires a list of bash scipts to run")

        new_inst = Instruction(InstructionType.BASH_SCRIPT, bash_scipts)
        self._inst_list.append(new_inst)

    def add_support_files(self, support_files: list):
        """Add a support files to update archive.

        Parameters
        ----------
        support_files: list
            A list of support files to add to the update.
        """

        for s_file in support_files:
            if isfile(s_file):
                raise ValueError(" {} was not found".format(s_file))

        new_inst = Instruction(InstructionType.SUPPORT_FILE, support_files)
        self._inst_list.append(new_inst)

    def status(self):
        """Print the contexts of instructions list"""

        for i in self._inst_list:
            print(i)

    def make_update_archive(self):
        """Make the update archive"""

        # download deb files
        self._cache.fetch_archives()

        # replace package name with deb filepath in instruction obj
        for inst in self._inst_list:
            if not inst.type == InstructionType.DPKG_INSTALL:
                continue

            for i in range(len(inst.items)):
                found = False
                for deb_file in listdir(DOWNLOAD_DIR):
                    if not deb_file.endswith(".deb"):
                        continue

                    if deb_file.startswith(inst.items[i] + "_"):
                        inst.items[i] = DOWNLOAD_DIR + deb_file
                        found = True
                        break

                if found is False:
                    break

        print("Making tar")

        update_file = create_update_archive(self._board, self._inst_list, "./")

        print("{} was made".format(update_file))

        # option to move generate updates to the update cache
        command = input("-> Save copy to update cache [Y/n]: ")

        if command == "Y" or command == "y" or command == "yes":
            try:
                copyfile(update_file, UPDATE_CACHE_DIR + basename(update_file))
            except:
                print("An error occurred saving the copy to update cache")
            else:
                print("{} was added to update cache".format(update_file))