$Id: installtool_doc.txt 35334 2008-01-25 14:33:40Z cray $
$URL: https://code.keysolutions.ru/svn/ks.installtool/tags/installtool-1.0.2/src/ks/installtool/installtool_doc.txt $

Руководство по работе с продуктом installtool:

    Постановка задачи и способ решения:
    
        Каждый человек, работавший с Zope 3 хотя бы некоторое время, не мог не
        обратить внимания на то, что перед использованием того или иного
        продукта постоянно приходится выполнять ряд заранее определённых
        действий, без которых работа продукта невозможна. Примерами подобных
        действий могут являться: создание сайта в некоторой папке, установка
        и регистрация под нужными именами коннекторов к базам данных,
        утилиты подключаемых аутентификаций, генератора уникальных
        идентификаторов и тому подобных компонентов. Для функционирования
        полноценного сайта порой возникает необходимость выполнения десятков
        и сотен таких операция.
        
        Подобный процесс установки может стать настоящим мучением, особенно
        в том случае, когда последовательность установки компонентов не
        является достаточно документированной, а компоненты при этом
        являются так или иначе зависимыми друг от друга: установка
        компонента A требует установки компонентов B и C, а компонент С в
        свою очередь не может работать без компонента D.
        
        Дополнительная сложность вызывается фактом, что компоненты и
        продукты zope представляют собой относительно независимые
        строительные блоки сайтов и порталов. Размещая и настраивая одни и
        те же компоненты различным образом, можно легко получить множество
        самых разнообразных решений, что, конечно же, не может не радовать,
        но приводит к тому, что развёртывание среды для каждого отдельного
        сайта становится отдельной процедурой со своей спецификой.
        
        Всё вышесказанное не является катастрофической проблемой до тех пор,
        пока не возникает необходимость частого выполнения всех требуемых
        действий (например, при отладке некоторого модуля сложной системы). В
        этом случае процесс разработки ощутимо тормозится, его сроки
        увеличиваются и разработчику становится трудно сосредоточиться на
        проблемах разработки.
        
        Очевидным выходом из сложившейся ситуации является использование
        такого продукта, который позволит автоматизировать процесс
        установки системы, запросив у администратора лишь те данные,
        без которых установка невозможна, а всю последовательность стандартных
        действий по добавлению, регистрации и установке свойств используемых
        утилит и продуктов выполнит самостоятельно, учитывая все зависимости
        между продуктами и утилитами. Данный подход позволит получить ряд
        очевидных преимуществ, в число которых входят, прежде всего, экономия
        времени разработчика и исключение ошибок, неизбежно возникающих при
        многократном выполнении рутинных действий.
        
        Все вышеуказанные проблемы легко решаются благодаря использованию
        продукта компании "Ключевые решения" installtool.

        Цель installtool - предоставление средств, которые позволят
        произвести настройку объектной среды "одним кликом".
    
    Основные понятия и принципы работы:
    
        Продукт installtool предназначен для автоматической настройки
        объектной среды внутри сервера приложений Zope3 (т.е. копирование
        файлов в нужные папки lib/python и etc/site-packages придётся все-же
        сделать руками).
        
        Решение на основе installtool включает в себя набор необходимых
        интерфейсов, схем, форм и скриптов. Интерфейсы в данном случае
        выполняют свои стандартные функции, позволяя привязать инструмент к
        нужному контексту. На основании объявленных схем, с помощью
        специально разработанных директив продукта installtool, происходит
        создание форм, используя которые, пользователь может указать все
        требуемые для установки данные. После заполнения форм в дело
        вступают скрипты, каждый из которых выполняет запрограммированную
        в нем работу.
        
        Каждый из скриптов декларирует цель и свои зависимости.
        Скрипт может достичь цели посредством некоторого реализованного
        в нем алгоритма, если удовлетворены его зависимости. Удовлетворение
        зависимостей есть цель других скриптов. Именно поэтому
        в директиве script продукта installtool для каждого
        выполняемого при установке скрипта следует указывать его имя
        (собственную цель) и список имен других скриптов (список зависимостей).

        Скрипты могут декларироваться в любом порядке, потому что перед
        их выполнением будет выполнена сортировка по зависимостям, которая
        может привести к нескольким результатам:

            Сортировка успешна -- т.е. найдена такая линейная
                последовательность скриптов, в которой каждый скрипт
                выполняется после скриптов, удовлетворяющих его зависимости;

            Сортировка провалилась -- найден цикл в зависимостях. В этом случае
                при попытке произвести установку появится сообщение об ошибке
                примерно следущего вида::

                    Ошибка
                    Не могу разрешить зависимости в одном из следующих скриптов:
                    имя_скрипта1 зависит от [u'имя_скрипта2']
                    имя_скрипта2 зависит от [u'имя_скрипта1']
            
            Сортировка провалилась -- найдены цели, не являющиеся
                зависимостями. В этом случае при попытке произвести установку
                появится сообщение об ошибке примерно следующего вида::

                    Несуществующая зависимость!
                    Unknown dependency имя_скрипта1 of имя_скрипта2

            
        В первом случае будет выполнена попытка выполнения всех скриптов, если
        какой-то скрипт провалится - то будет выведен список всех выполенных скриптов
        и ошибочный скрипт. В двух неудачных случаях сортировки будет выведено
        соответствующее соообщение об ошибке (примеры смотрите выше) со списком
        скриптов, которые не удалось отсортировать.
               
    Синтаксис и семантика zcml-инструкций:
    
        Как было сказано выше, продукт installtool позволяет при написании
        инсталляторов использовать некоторый набор zcml-директив, упрощающих
        работы по настройке внутриобъектной среды. 
               
        На написание скриптов есть одно важное соглашение: installtool
        создает корневой объект и все работы по настройке и созданию
        объектов должны выполнятся скриптами должны выполнятся только внутри
        этого контекста. Это соглашение между разработчиками возникло из-за
        того, что малейшие попытки его нарушения вызовут страх пользователей
        перед "порчей" системы от запуска скриптов, корректирующих ее в
        заведомо неизвестной среде. Вероятность ошибок при этом крайне
        высока, а их диагностика - практически невозможна.
        
        Поэтому никогда не меняйте ничего вне переданного в скрипт контекста.

        Директива browser:factoryform :
            
            Директива browser:factoryform включает в себя несколько
            атрибутов и поддирективу <property/>. Список атрибутов
            следует ниже:
                        
                name -- уникальное имя создаваемой фабрики. Сама фабрика
                        с указанным именем будет создана как класс в модуле
                        installtool.factories. Таким образом, полным именем
                        фабрики будет указание полного пути к ней в виде
                        installtool.factories.имя_фабрики.

                root -- корневой объект процедуры инсталляции: прежде всего
                        создаётся он, и только потом всё остальное.

                schema -- указывается схема для генерации формы. Поля,
                        объявленные в этой схеме, будут впоследствии
                        отображены на форме инсталляции продукта (в
                        данном случае demo)

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

                view -- url формы.

                title -- заголовок формы и метка для меню.

                description -- описание формы.

                permission -- права доступа на вызов формы.
        
            Вложенные директивы property (их может быть столько, сколько надо),
            задают константы для установки, которые будут переданы
            скриптам установки в виде параметров с соответствующими
            именами и значениями. Атрибуты директивы property:
                                        
                name -- имя параметра, который будет передаваться в
                            установочные скрипты
                    
                value -- значение передаваемого параметра
                
            Пример директивы ::

                <browser:factoryform
                   name="demo" 
                   root="zope.app.folder.folder.Folder"
                   schema=".interfaces.IDemo" 
                   for="*" 
                   view="calldemo.html"
                   title="Demo" 
                   description="Demo Demo"
                   permission="zope.ManageContent">
                 <property name="par1" value="11"/> 
                 <property name="par2" value="12"/>
                 <property name="par2" value="12"/> 
                 <property name="par4" value="14"/>
                 </browser:factoryform>

            К директиве декларции фабрики следует добавить набор директив,
            декларирующих скрипты, используемые этой фабрикой. Для
            декларации используется директива <script/> со следующими
            аргументами:

                factory -- имя фабрики, созданной с помощью директивы
                    factoryform (для приведённых выше примеров это имя demo,
                    указанное в атрибуте name директивы factoryform).

                name -- уникальное имя, присваиваемое инсталляционому скрипту.
                 
                required -- список имён скриптов, которые должны быть
                    выполнены до выполнения текущего скрипта.
                     
                script -- полный путь к скрипту, выполняющему требуемые от него
                    при установки действия.

            Пример ::
                           
               <script
                   factory="demo"
                   name="C"
                   requires="B"
                   script=".scripts.scriptC"
                   />
    
    Пример использования:
    
        В качестве конкретного примера, а также для иллюстрации вышесказанного,
        рассмотрим пример использования продукта installtool для установки сайта,
        состоящего из некоторой папки, внутри которой расположены 20 других
        папок с именами вида name000, name001, ..., name019. Всё это, как и
        заявлялось, будет реализовано минимальным числом кликов.
        
        Для того, чтобы наглядно увидеть демонстрацию возможностей installtool
        на примере продукта demo, возьмите исходники продукта installtool по
        адресу::
        
            https://code.keysolutions.ru/svn/installtool/trunk/installtool/

        Содержимое папки demo и есть продукт, написанный на основе installtool.
        Скопируйте файлы из папок installtool/etc и installtool/demo/etc
        в папку ZOPE_INSTANCE/etc/site-packages. Запустите zope, зайдите в него.
    
        Для определённости перейдите в корневую папку. В меню "Добавить" слева
        найдите пункт Demo, щёлкните по нему. На экране появится форма,
        сгенерированная на основе полей схемы installtool.demo.interfaces.IDemo.
        Данные в полях можно оставить без изменения, так как на дальнейшую
        установку их значения не влияют, и они используются лишь в
        иллюстративных целях. Для определённости, впишите в поле "Название
        объекта" значение Root и нажмите кнопку "Добавить". На этом действия по
        установке продукта завершены!

        В корневой папке найдите папку с именем Root, зайдите в неё, убедитесь,
        что внутри расположены 20 папок с требуемыми именами. Сравните
        затраченное на это время со временем, которое вы затратили бы на
        создание подобной структуры папок руками и сделайте соответствующие
        выводы.
        
        Поясним проделанное. Зайдите в папку demo. Основными файлами продукта,
        помимо __init__.py и add.py, являются, как вы видите, configure.zcml,
        содержащий набор описанных выше директив, interfaces.py, содержащий
        схемы используемых форм и файл scripts.py, содержащий, собственно,
        скрипты, производящие установку.
        
        Как видно, для проделывания необходимых действий использовались лишь
        директивы factoryform и script. Все поля директивы factoryform
        подробно описаны выше и проблем с пониманием их использования возникнуть
        не должно.
        
        Проанализировав директивы script можно легко увидеть. что установка
        происходит четырьмя скриптами с именами scriptA, scriptB, scriptC,
        программный код которых расположен в файле scripts.py. Обратите
        внимание, что в файле scripts.py смысл имеет лишь код скрипта A, а
        остальные указаны в иллюстративных целях для проверки контроля
        зависимостей между скриптами.
        
        Из файла configure.zcml видно, что скрипт A нельзя выполнять до
        выполнения скрипта С, а скрипт C нельзя выполнять до выполнения скрипта
        B. Скрипт B не требует выполнения никаких других скриптов. Видно, что
        скрипты расположены не в том порядке, в каком их следует выполнять,
        однако, благодаря использованию топологической сортировки, скрипты
        вызываются на выполнение в единственно безошибочной последовательности:
        B, C, A.
    
        Файл interfaces.py содержит описание схемы (в данном случае одной),
        используемой для генерации формы добавления нашего демонстрационного
        продукта.
        
        Вот и всё!
     
    Заключение:
    
        Как видно из документа и приведённых в нём примеров, использование
        продукта installtool позволяет существенно сократить время,
        затрачиваемое на установку программного обеспечения под Zope3, что
        повышает удобство как разработчика, так и человека, который будет
        устанавливать тот или иной продукт.
        
    Планы на будущее:

        Продукт по мере сил совершенствуется, и на сегодняшний день можно
        выделить следующие направления его дальнейшего развития:

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

            - Проработать возможность исключения подцелей на этапе установки.
              Это может быть достаточно полезно, когда нет необходимости
              устанавливать систему целиком, а требуется лишь установка части
              элементов объектной среды будущей системы для установки версии
              с ограниченной функциональностью.
        
            - Сделать возможность создания "пакетов" скриптов, т.е. скриптов,
              которые объединяются не по явным названиям, а
              по абстрактным целям. Это позволит иметь некоторую взаимозаменямость
              скриптов при установке разных продуктов в систему. Например, можно
              будет иметь форму "Создать Сайт" которая создаст такой сайт, который
              позволяют продукты, установленные в системе.

    Приложение 1. Траверс ++etc++site:

        #TODO: Я тебе обрисую основную проблему. А ты потом подумаешь как это 
        описать. Итак проблема такая: несмотря на то, что локальный сайт-менеджер 
        создавался в рамках инсталлятора, часть кода _не_будет_ работать с ним,
        а будет вызывать сайт-менеджер, зарегестрированный как текущий при траверсе.
        Так устроен Zope - подробнее см. статью. Это не очень заметно при вызове
        _вашего_ кода, так как в нем всегда можно явно передать context в вызов
        функции, требующей сайт-менеджера. Но это может всплыть либо при нецеленаправленном (слово поменяй)
        вызове стороннего кода, либо при рабботе с плохо-сформированными объектами (например,
        не-ILocatable), к которым, в Zope3, к сожалению, относятся достаточно распространенные объекты,
        наприем File и Image. Типичное проявление ошибки: во время установки не происходит индексация
        создаваемых объектов. Или: во время установки происходит внезапное падение инсталлятора 
        по причине отсутствия затребованной утилиты. 


    
        Те их вас, кто пока не может понять, при чём тут траверс ++etc++site
        и использование installtool, вероятно, просто не подозревали о том,
        как важен этот вопрос при написании собственных инсталляторов. Перед
        дальнейшим прочтением этого приложения рекомендуется вдумчиво, хотя
        бы на один раз, а лучше на 2 или 3, ознакомиться вот с этой
        статьёй::
        
            http://zope3.ru/stati/raznye-melkie-zametki/kak-rabotaet-sitemanager-chto-by-obespechit-dostup-k-lokalnym-utilitam/
        
        У непросвещенных специалистов при использовании installtool может
        возникнуть примерно следующая проблема. Пишется инсталлятор, который
        создаёт корневую папку, куда будет инсталлироваться всё, что
        требуется. Внутри процедуры установки вызывается сайт-менеджер, куда
        добавляются и где регистрируются различные утилиты: всё это
        делается, работа инсталлятора на этом завершается. Специалист
        заходит в скин, в меню "Добавить" выбирает только что созданный
        инсталятор, заполняет на появившейся форме все нужные поля, нажимает
        кнопку. Инсталляция прошла успешно, корневой объект процедуры
        инсталляции появился, ошибок не возникло. Заходим в созданную, к
        примеру, папку, пытаемся найти там сайт-менеджер с установленными и
        зарегистрированными в нём утилитами, но не находим ни того ни
        другого.
        
        Что случилось? Ничего, так как все утилиты добавились и
        зарегистрировались, а весь вопрос в том, где они теперь. Как можно
        понять из статьи, у Zope есть глобальный сайт-менеджер, а есть куча
        локальных, но большинство разработчиков, кто-то по неопытности,
        кто-то по невнимательности, всегда об этом забывают и получают
        сайт менеджер с помощью sm = context.getSiteManager(). Результатом
        этого является то, что в переменной sm будет запомнен последний
        сайт-менеджер, пройденный при траверсе.
        
        Таким образом, проблема поиска созданных и зарегистрированных утилит
        упирается в ответ на вопрос: "Создавал ли я в папке, куда
        устанавливался набор продуктов, сайт-менеджер?" Если ответ: "Да,
        создавал", - то и описываемая проблема вас волновать не должна, так как
        всё созданное в sm, полученным вышеуказанным образом, будет создано в
        локальном сат-менеджере. Ну а если ответ был: "Нет, не создавал", -
        значит в этом и вся проблема, и сайт-менеджер надо просто создать.
        
        В большинстве случаев, корневым объектом процедуры инсталляции является
        папка (то есть то место, в сайт-менеджер которого и происходит
        установка всего, что требутся). Если это верно и для вашего случая,
        то программный код (взятый из реально существующего инсталлятора),
        который преобразует папку в сайт и создаёт в этой папке сайт-менеджер,
        возвращая сообщение об успехе, может быть следующим::
        
            from zope.app.component.hooks import setSite
            from zope.app.component import site
        
            def installSiteManager(context, **kw):
                """ Преобразует папку контекста в сайт
                """
                # Создаём локальный сайт-менеджер
                sm = site.LocalSiteManager(context)
                # Устанавливаем созданный менеджер сайта в контекст
                context.setSiteManager(sm)
                # Делаем контекст сайтом
                setSite(context)
                # Выполнено успешно
                return "Success"

        Впоследствии получить созданный сайт-менеджер можно будет уже известным
        способом::
        
            sm = context.getSiteManager()

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

        Правильная расстановка зависимостей - краеугольный камень, заложив
        который нужным образом вы, безусловно, гарантируете себе сокращение
        проблем как минимум в несколько раз.
        
        При расстановке зависимостей обратите внимание на следующие факторы,
        которые влияют на расстановку зависимостей для некоторого
        инсталляционного скрипта:
        
            1. Зависимость одного инсталляционного скрипта от другого следует
               указывать тогда, когда другой инсталляционный скрипт явно
               использует утилиты или объекты, устанавливаемые первым скриптом.
               Например, скрипт A устанавливает и регистрирует в сайт-менеджере
               экземпляр утилиты IntIds, а скрипт B в процессе работы требует
               наличия установленной утилиты IntIds.
               
            2. Зависимость одного инсталляционного скрипта от другого следует
               указывать тогда, когда другой инсталляционный скрипт неявно
               использует утилиты или объекты, установленные первым скриптом.
               Например, скрипт A устанавливает и регистрирует в сайт-менеджере
               экземпляр утилиты IntIds, а скрипт B устанавливает продукт,
               который при установке требует наличия утилиты IntIds. Подобные
               зависимости очень коварны, поэтому при написании инсталляторов
               и указании зависимостей на них следует обратись особое внимание,
               так как они могут не быть продиагностировыны продуктом
               installtool, а вылезти как самостоятельная ошибка, хотя ошибка
               будет именно в неверно установленных зависимостях.
    
        Практика показывает, что из-за ошибок при указании зависимостей типа 2
        может быть потеряно до несольких часов времени, которое можно было
        использовать с гораздо большей пользой, чем отлов порождений
        собственной невнимательности.
    
    Приложение 3. О нецелесообразности установки всех требуемых свойств

        В корректно написанных продуктах, большая часть
        атрибутов имеет какое-то значение по умолчанию, спецально подобранное
        так, чтобы удовлетворить нужды большинства людей, использующих этот
        продукт, благодаря чему достаточно просто вызвать конструктор класса
        без каких-либо параметров или ключевых аргументов, в результате чего у
        созданного объекта будут нужным образом  установлены значения всех
        необходимых атрибутов.
        
        Явная установка атрибутов - не ошибка, однако выполнять её везде,
        где только можно - спорная концепция, по двум следующим причинам:
        
            1.  Лишний код, который не имеет никакого смысла, никогда не
                повысит качество конечного продукта;
                
            2.  До явной установки атрибута, обычно, используется атрибут
                _класса_. Это означает, что при обновлении продукта, этот
                атрибут будет обновляться (так как обновляется определение
                класса на диске) и ваш объект сразу же начнет работать 
                по новому (а иначе потребуется явно влезать и менять атрибут).
                
                Это не стоит делать краеугольным камнем
                вашей работы, но тем не менее, в период интенсивной
                разработки ПО, использование атрибутов класса сильно
                сократит затраты времени при миграции тестового сервера на
                новые версии ПО.
        
        Итак, устанавливайте явно только значения тех атрибутов, которые не
        устанавливаются нужным образом по умолчанию, а все остальные оставьте
        в покое.

    Приложение 4. Об опыте установки некоторых частообновляемых служб

        При написании инсталляторов иногда возникает ситуация, когда скрипт
        устанавливает регулярную структуру объектов большого размера. В
        этом случае писать инсталлятор как обычную программу (делая для
        каждого узла этой структуры явные вызовы конструкторов и прочего
        кода) оказывается нерациональным: лучше формализовать некототорое
        описание этой структуры и написать интерпретатор этого описания.
        Приведем пример - создание древовидного рубрикатора. Каждый узел
        рубрикатора имеет название и идентификатор. Наивное решение выглядит
        так::
        
            context['a'] = Folder()
            context['a'].title = "Название А"
            context['b'] = Folder()
            context['b'].title = "Название Б"
            context = context['a'] 
                        
            И т.п.
            
        Чуть более удобное::
        
            schema = {
                "a/b/c" : "Название abc",
                "a/b"   : "Название ab",
                "a/a"   : "Название aa",
            }            
            # NOT TESTED!!!
            for path in sorted(schema.keys()) :
                items = path.split("/")
                end = items[-1]
                ob = context
                for item in items[0:-1]
                    ob = ob[item]
                ob[end] = Folder()
                ob[end].title = [path]
                    

        Различие, на первый взгляд, почти незаметно, но представьте себе
        рубрикатор из 400 объектов? Представьте, что в нем устанавливается
        3-4 свойства, а не только название? Представьте, что создание
        каждого объекта требует еще нескольких дополнительных операций? 
        Представили? Так вот этот образ может оказаться реальностью в
        _любом_ реальном проекте.
    
Конец документа
