Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Архив1 / docx53 / note - 7.docx
Скачиваний:
31
Добавлен:
01.08.2013
Размер:
457.93 Кб
Скачать
    1. Реализация системы управления пакетами

Реализация системы управления пакетами была условно разделена на три части:

  1. Работа с удаленным репозиторием с формулами пакетов

  2. Реализация базовых команд для управления пакетами:

    1. Freeze – вывод всех установленных пакетов

    2. Cache – вывод всех доступных для установки пакетов

    3. Outdated – вывод всех установленных пакетов требующих обновления

    4. Update – обновления формул из репозитория

    5. Install – установка выбранного пакета

    6. Uninstall – удаление выбранного пакета

    7. Upgrade – обновление выбранного пакета

  3. Реализация тестового консольного клиента

Основной момент реализации заключается в обновлении доступных пакетов, для хранения формул используется удаленный репозитории под управление системы контроля версий Git, для того чтобы забрать интересующие формулы нам необходимо связать по одному из доступных протоколов (http, https, …) и «склонить» содержимое репозитория, в дальнейшем, при наличии локального репозитория на машине пользователя, будет происходить только обновление локального репозитория посредством команд pull.

Приведенный ниже код позволяет провести все вышеперечисленные операции, а также обрабатывает возможные исключения которые могут возникнуть в ходе данной операции.

#!/usr/bin/env python

# -*- coding: utf-8 -*-

#

# Copyright (c) 2012 Procyon <https://github.com/Gr1N/procyon>

#

# Permission is hereby granted, free of charge, to any person obtaining

# a copy of this software and associated documentation files (the

# "Software"), to deal in the Software without restriction, including

# without limitation the rights to use, copy, modify, merge, publish,

# distribute, sublicense, and/or sell copies of the Software, and to

# permit persons to whom the Software is furnished to do so, subject to

# the following conditions:

#

# The above copyright notice and this permission notice shall be included

# in all copies or substantial portions of the Software.

#

# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,

# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF

# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.

# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY

# CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,

# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE

# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

from __future__ import unicode_literals

from git.cmd import Git

from git.exc import InvalidGitRepositoryError, GitCommandError, NoSuchPathError

from git.repo.base import Repo as GitRepo

from procyon import settings as procyon_settings

__all__ = (

'update_repo',

)

def open_or_clone_repo():

"""Returns opened or cloned repo and 'True' flag if operation successful

else 'None' object and 'False' flag.

"""

try:

return GitRepo(path=procyon_settings.REPO_PATH), True

except (InvalidGitRepositoryError, NoSuchPathError):

pass

try:

Git(procyon_settings.PROCYON_PATH).clone(procyon_settings.REMOTE_REPO)

except GitCommandError:

return None, False

return GitRepo(path=procyon_settings.REPO_PATH), True

def update_repo():

"""Returns 'True' and hexshas if operation successful else 'False' and

none-hexshas. If raises some erros when repo updating, return 'False',

current repo hash and none-hexsha.

"""

repo, successful = open_or_clone_repo()

if not successful:

return False, None, None

hexsha = lambda r: r.heads.master.commit.tree.hexsha

before_up_hexhsha = hexsha(repo)

try:

origin = repo.remotes.origin

origin.pull()

except GitCommandError:

return False, before_up_hexhsha, None

except AssertionError:

pass

after_up_hexsha = hexsha(repo)

return True, before_up_hexhsha, after_up_hexsha

Следующий ключевой момент это формулы, формулы представляют собой Python-модуль в котором описаны все ключевые моменты пакета:

  • Имя пакета

  • Информация о пакете

  • Версия пакета

  • Домашняя страница

  • Ссылка по которой будет скачиваться пакет

  • Md5sum для проверки скаченного архива

Так как любая формула представляет собой Python-модуль это дает широкие возможности мейнтейнерам пакетов, т.к. они могут определить в них все что позволяет язык программирования.

Ниже приведена формула абстрактного пакета:

from procyon.pkg.models import Formula as BaseFormula

class Formula(BaseFormula):

name = 'Test awesome package'

info = 'This package can help you to save the world!!'

home_page = 'http://how-help-to-save-the-world.com/'

version = '42'

url = 'http://download.me/help.zip'

md5sum = 'ff4157cded0b84234a8235e3b4858572'

Для хранения информации об установленных пакетах была спроектирована и реализована БД, в качестве БД используется SQLite.

SQLite — легковесная встраиваемая реляционная база данных. Слово «встраиваемый» означает, что SQLite не использует парадигму клиент-сервер, то есть движок SQLite не является отдельно работающим процессом, с которым взаимодействует программа, а предоставляет библиотеку, с которой программа компонуется и движок становится составной частью программы. Таким образом, в качестве протокола обмена используются вызовы функций (API) библиотеки SQLite. Такой подход уменьшает накладные расходы, время отклика и упрощает программу. SQLite хранит всю базу данных (включая определения, таблицы, индексы и данные) в единственном стандартном файле на том компьютере, на котором исполняется программа. Простота реализации достигается за счёт того, что перед началом исполнения транзакции записи весь файл, хранящий базу данных, блокируется; ACID-функции достигаются в том числе за счёт создания файла журнала.

Для удобства работы с БД, был использован ORM peewee, который позволяет не писать SQL-запросы, т.к. все запросы обернуты соответствующими метода. Спроектированная таблица БД, содержит следующие поля:

  • Name – имя установленного пакета

  • Formula_name – имя формулы посредством которой был установлен пакета

  • Version – текущая версия установленного пакета

  • Updated_at – дата последнего обновления пакета

Ниже приведена текущая реализация БД с использованием ORM peewee:

database = os.path.join(procyon_settings.PROCYON_PATH, procyon_settings.PACKAGES_DB_NAME)

database = peewee.SqliteDatabase(database)

database.connect()

class Package(peewee.Model):

name = peewee.CharField(db_index=True, unique=True)

formula_name = peewee.CharField(db_index=True, unique=True)

version = peewee.CharField()

updated_at = peewee.DateTimeField(default=datetime.now())

class Meta:

database = database

if not Package.table_exists():

Package.create_table()

Приведенный код реализует соединение с БД на локальном диске пользователя и в случае если в БД еще не созданы необходимые таблицы для работы, то создает их.

Реализация команд:

  • Freeze – данная команда позволяет просмотреть все установленные пакеты посредством разрабатываемой системы управления пакетами. Команда реализует запрос к БД который выбирает все доступные значения. Ниже приведена реализация команды freeze:

def get_installed_packages():

"""Returns dictionary with all installed packages.

"""

installed = {}

for entry in Package.select():

installed.setdefault(entry.name, {

'formula_name': entry.formula_name,

'version': entry.version,

'updated_at': entry.updated_at,

})

return installed

  • Cache – данная команда позволяет просмотреть все доступные для установки пакеты. Команда смотрит директорию где хранятся формулы, достает из них необходимую информацию и возвращает словарь с полученными данными. Ниже приведена реализация команды cache:

def get_available_packages():

"""Returns dictionary with available to install packages from repo.

"""

available = {}

for modulename in os.listdir(procyon_settings.REPO_PATH):

if modulename != '__init__.py' and modulename.endswith('.py'):

package_name, package_data = get_package_data(modulename)

if package_name and package_data:

available.setdefault(package_name, package_data)

return available

  • Outdated – данная команда получает все установленные пакеты из БД, получает все доступные пакеты для установки и сравнивает версии, если версия пакета устарела, то этот пакет помечается как устаревший. Ниже приведена реализация команды outdated:

def check_version(available_version, installed_version):

available_lpart, dot, available_rpart = available_version.partition('.')

installed_lpart, dot, installed_rpart = installed_version.partition('.')

if int(available_lpart) > int(installed_lpart):

return True

elif int(available_lpart) < int(installed_lpart):

return False

elif len(available_rpart) == 0:

return False

elif len(available_rpart) != 0 and len(installed_rpart) == 0:

return True

else:

return check_version(available_rpart, installed_rpart)

def get_outdated_packages():

"""Returns dictionary with outdated installed packages.

"""

installed = get_installed_packages()

available = get_available_packages()

outdated = {}

for package_name, package_data in installed.iteritems():

available_version = available.get(package_name, {}).get('version', None)

installed_version = package_data.get('version')

if available_version and check_version(available_version, installed_version):

package_data.update({

'available_version': available_version,

})

outdated.setdefault(package_name, package_data)

return outdated

  • Search – команда позволяет искать доступные пакеты по имени, алгоритм команды схож с реализацией команды cache, с учетом выборки по имени пакета. Ниже приведена реализация команды search:

def get_available_packages_by_name(name):

"""Returns dictionary with available to install packages from repo with

specified package name.

"""

def prepare_name(name):

name = str(name).lower()

for to_replace in ['-', '_', '/', ' ', '[', ']']:

name = name.replace(to_replace, '')

return name

name = prepare_name(name)

available = {}

for package_name, package_data in get_available_packages().iteritems():

prepared_pkg_name = prepare_name(package_name)

if name in prepared_pkg_name or prepared_pkg_name in name:

available.setdefault(package_name, package_data)

return available

elif int(available_lpart) < int(installed_lpart):

return False

elif len(available_rpart) == 0:

return False

elif len(available_rpart) != 0 and len(installed_rpart) == 0:

return True

else:

return check_version(available_rpart, installed_rpart)

def get_outdated_packages():

"""Returns dictionary with outdated installed packages.

"""

installed = get_installed_packages()

available = get_available_packages()

outdated = {}

for package_name, package_data in installed.iteritems():

available_version = available.get(package_name, {}).get('version', None)

installed_version = package_data.get('version')

  • Install – команда посредством которой происходит непосредственно установка пакетов. Команде на вход подается название пакета который необходимо установить, в первую очередь происходит проверка на то, что такой пакет вообще существует, в случае успешной проверки происходит загрузка архива с пакетом по разрешенным протоколам (на данный момент это https и http), далее если в описании указана md5sum со идет проверка на совпадение хэшей, если все удачно, то происходит завершающий этап установки. Ниже приведена реализация команды install:

# procyon/pkg/logic.py

def install_package(name):

available = get_available_packages()

if name not in available:

return InstallationStatuses.FORMULA_NOT_FOUND

packages = [package.name for package in Package.select().where(Package.name == name)]

if len(packages) > 1:

return InstallationStatuses.INSTALL_ERROR

elif packages and packages[0] == name:

return InstallationStatuses.ALREADY_INSTALLED

package = available.get(name)

formula = import_formula_module(package.get('formula_name'))

if not formula:

return InstallationStatuses.BAD_FORMULA

status = formula.install()

if status != InstallationStatuses.INSTALL_OK:

return status

Package.create(

name=formula.name,

formula_name=package.get('formula_name'),

version=formula.version

)

return status

# procyon/pkg/models.py

class Formula(object):

name = None

info = None

version = None

homepage = None

url = None

md5sum = None

def check_items(self):

if self.name and self.info and self.version and self.url:

return True

return False

def _check_md5sub(self, tmp_file):

md5 = hashlib.md5()

with open(tmp_file, 'rb') as f:

for chunk in iter(lambda: f.read(128 * md5.block_size), b''):

md5.update(chunk)

return md5.hexdigest() == self.md5sum

def _download(self):

allowed_schemes = [

'http',

'https',

]

scheme = urlparse(self.url).scheme

if scheme not in allowed_schemes:

return InstallationStatuses.BAD_URL, None

try:

tmp_file, info = urlretrieve(self.url)

except IOError:

return InstallationStatuses.DOWNLOAD_ERROR, None

if not zipfile.is_zipfile(tmp_file) and not tarfile.is_tarfile(tmp_file):

return InstallationStatuses.BAD_FILE_TYPE, None

if self.md5sum and not self._check_md5sub(tmp_file):

return InstallationStatuses.MD5SUM_CHECK_ERROR, None

return InstallationStatuses.DOWNLOAD_OK, tmp_file

def _extract(self, tmp_file):

if zipfile.is_zipfile(tmp_file):

arc = zipfile.ZipFile(tmp_file)

elif tarfile.is_tarfile(tmp_file):

arc = tarfile.TarFile(tmp_file)

else:

return InstallationStatuses.BAD_FILE_TYPE

install_dir = os.path.join(procyon_settings.INSTALL_PATH, self.name)

if not os.path.exists(install_dir):

os.makedirs(install_dir)

try:

arc.extractall(path=install_dir)

except (zipfile.BadZipfile, zipfile.LargeZipFile, tarfile.ReadError, tarfile.ExtractError):

return InstallationStatuses.EXTRACT_ERROR

return InstallationStatuses.EXTRACT_OK

def install(self):

if not self.check_items():

return InstallationStatuses.BAD_FORMULA

status, tmp_file = self._download()

if status != InstallationStatuses.DOWNLOAD_OK:

return status

# TODO: install dependencies

status = self._extract(tmp_file)

if status != InstallationStatuses.EXTRACT_OK:

return status

return InstallationStatuses.INSTALL_OK

  • Uninstall – команда реализующая функционально полностью противоположную функциональности команды install, т.е. удаление пакетов. Команде на вход подается название пакета который необходимо удалить, в первую очередь происходит проверка, а установлен ли такой пакет вообще, если установлен, то происходит инициализация процесса удаления пакета: удаляется из БД запить об этом пакете, а также удаляется сам пакет с локального диска пользователя. Ниже приведена реализация команды uninstall:

# procyon/pkg/logic.py

def uninstall_package(name):

installed = get_installed_packages()

if name not in installed:

return InstallationStatuses.NOT_INSTALLED

package = installed.get(name)

formula = import_formula_module(package.get('formula_name'))

if not formula:

return InstallationStatuses.BAD_FORMULA

status = formula.uninstall()

if status != InstallationStatuses.UNINSTALL_OK:

return status

package = Package.get(name=name)

package.delete_instance()

return status

# procyon/pkg/models.py

class Formula(object):

name = None

...

def uninstall(self):

if not self.check_items():

return InstallationStatuses.BAD_FORMULA

install_dir = os.path.join(procyon_settings.INSTALL_PATH, self.name)

if not os.path.exists(install_dir):

return InstallationStatuses.NOT_INSTALLED

shutil.rmtree(install_dir)

# TODO: uninstall dependencies

return InstallationStatuses.UNINSTALL_OK

Текущая реализация системы управления пакетами представляет собой небольшую библиотеку, т.е. api, необходимое для управления пакетами, описанные выше команды представляют собой основу этого api. Т.к. разработка велась на языке Python, то данная библиотека представляет собой не что иное как пакет который подготовлен для установки в окружения пользователя или посредством install.py скрипта или посредством pip’a.

Как уже говорилось выше разрабатываемая система управления пакетами это всего лишь библиотека и без какой-либо обертки для упрощения использования конечного пользователя, не несет никакой ценности. Поэтому в качестве демонстрации возможностей разрабатываемой системы, был разработан тестовый консольный клиент, клиент полноценно использует все возможности библиотеки. Ниже представлен вывод команды help, которая демонстрирует доступный функционал клиента:

procyon/test_client(branch:master) » python client.py –h

usage: procyon [-h] command [parameter [parameter ...]]

Package manager for OSTIS project.

positional arguments:

command command name

parameter command parameter (file url or command name)

optional arguments:

-h, --help show this help message and exit

Supported commands:

set <url> - Sets url of repo containing packages.

update - Updates package list.

install <packages> - Installs new package.

remove <packages> - Removes installed package.

upgrade <packages> - Installs new version of all installed packages.

search <package> - Searches packages with key word.

list <list command> - Shows list of specified packages

Supported list commands:

installed - Shows list of currently installed packages.

available - Shows list of available to install packages.

outdated - Shows list of outdated packages.

Ниже, для примера, приведен вывод команды на получение всех доступных пакетов для установки:

procyon/test_client(branch:master) » python client.py list available

Available package list:

Test awesome package v42

This package can help you to save the world!!

Test2 v12

ololo2

Как можно увидеть доступно два пакета, а также выведена краткая информация о них и их версии.

Соседние файлы в папке docx53