[Из песочницы] Script-server. WebUI для удалённого запуска ваших скриптов

Всем привет. В данной статье я бы хотел рассказать про свой домашний проект. Если коротко: Script server является веб-сервером для предоставления пользователям доступа к вашим скриптам через web-интерфейс. Сервер и скрипты запускаются локально, а параметризуются и показываются удалённо.
7deddad32683429e8903690aa4ae331c.png


Предыстория


На новом месте работы, первые мои задачи были весьма рутинны и однообразны. Схематично они выглядели так:
  1. Создать файл my_file.txt
  2. Прописать туда новую конфигурацию
  3. Задеплоить на сервер
  4. Закомиттить

Шагов было больше и они были посложнее, но уже через пару таких итераций лень победила и для всех повторяющихся задач были написаны shell-скрипты, которые запускались с несколькими параметрами и иногда запрашивали дополнительные входные данные в процессе работы. В то же время, рабочий процесс стал выглядеть примерно так:
  1. спросить у руководителя проекта нужные параметры
  2. запустить скрипт
  3. доложить о готовности

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

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

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

На пути создания


Схема работы


64b039c185de4b9a8793b16bc9992ba1.png

Администратор сервера (т.е. я) создаёт файлы конфигурации для каждого скрипта, в которых описывает предназначение скрипта, путь запуска и требуемые параметры. Эти файлы конфигурации используются сервером для предоставления данных о скриптах пользователю, а также для их запуска. Информация на страницу пользователя передаётся Ajax запросами.

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

Веб сервер служит прослойкой между этим обработчиком и страницей, и через веб-сокеты осуществляет обмен данными.

Выбор инструментов


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

В начале разработки для сервера я использовал Flask, но не смог сделать (читай разобраться) с асинхронностью и отслеживанием подключения/отключения клиентов, поэтому довольно быстро перевёл все на Tornado.

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

Web страница представляет собой «одностраничное приложение» с минимумом HTML и созданием контента в JS по Ajax запросам.

Первая эксплуатация и улучшения


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

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

Интересный момент: для некоторых мелких задач (связанных с работой в консоли и со скриптами) я также перестал пользоваться консолью, а перешёл на этот UI.Т. е. у меня заведено несколько конфигураций скриптов, которые я использую чаще, чем другие люди.

Эксплуатация реальным пользователем


Спустя некоторое время локального тестирования (мною и другими разработчиками), инструмент был наконец предоставлен руководителю проекта, который начал потихоньку им пользоваться. И надо отметить, что он очень доволен, т.к. это экономит в том числе и его время. Процесс «внедрения» и обучения пользователя занял примерно 5 минут.

Из-за особенностей процессов и ограничений, Script server используется в основном для тестового окружения. Но некоторые его части уже стали использоваться и для Production (в той части, где это безопасно).

Основные камни


Ниже приведён список проблем и моих личных открытий, которые мне больше всего запомнились.

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

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

Отслеживание отключения пользователей. Именно из-за этой проблемы отказался от Flask: в тот момент я делал не на вебсокетах, а на SSE (ну почти). Однако позже я все же перевёл передачу input/output скрипта на вебсокеты, так что возможно и Flask бы подошёл. В Торнадо же просто можно подписаться на закрытие вебсокета.

Считывание выходных данных выполнения скрипта. Тут была проблема с тем чтобы считывать только текущие доступные данные и отправлять на клиент. Считывать по строкам нельзя, т.к. например «read -p input_prompt» не переносит на следующую строку. Считывать по символам можно, но отправлять так много запросов на клиент не стоит. Считывание буфером даёт строку обрезанную в непонятных местах (а ещё и в случае UTF-8 это порождает неправильные символы). Текущее решение изобилует набором компромиссов и костылей, но в целом это побуферное считывание с отключение блокировки чтения.

Output скриптов в (не)терминальном режиме. Честно говоря для меня это было открытием: output скриптов может отличаться если запускать их в терминале или вне. Например тот же…

read -p input_prompt

… показывает input_prompt только в терминальном режиме. Не очень хорошо чего-либо ждать от пользователей, не показывая им чего именно. Пришлось разбираться с pty, чтобы обманывать запускаемые скрипты. По этой причине у меня сейчас два типа обёрток для запуска скриптов: с поддержкой терминала и без. Первый включается по-умолчанию, второй можно включить с помощью конфигурации (оставил его на всякий случай, если терминальный режим будет сбоить).

Автоскролл выходных данных скрипта. С одной стороны это решается довольно просто, с другой стороны были проблемы с настройкой автоматического отключения этого (если пользователь скроллит вручную или выделяет текст), а также с тенью, вводом пользователя и т.п. Ничего экстраординаорного, но несколько часов потратить пришлось.

Настройка панели с выходными данными скрипта таким образом, чтобы она занимала всё доступное место, но при этом никогда не выходила за границы окна. Таким образом страница всегда умещается в окно и единственный скролл добавляется именно внутри output панели. Для меня это было особенно важно, т.к. я категорично не люблю скроллы в скроллах.

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

htmlElement.innerText += "text";

Дело в том, что получение innerText это сама по себе довольно ресурсоёмкая операция, которая пытается представить входящий html как строку, даже если это просто текст. Если вызывать эту операцию пару сотен раз и для тысячи символов, то эффект не заметен, но в моем случае данных было гораздо больше и браузер просто зависал на несколько минут от такой нагрузки. Решилось изменением кода на следующий:
var textNode = document.createTextNode("text");
htmlElement.appendChild(textNode);

Висящие процессы, т.е. никто ничего не выполняет, но в процессах висит выполняемый скрипт. Причины были разные, например: пользователь закрыл страницу, а сервер не обрабатывает это. Или не закрытый дескриптор файла. Или незакрытые дочерние процессы. И это не считая других очевидных багов. Но устранялось все гуглёнием, изучение и исправлением.

Безопасность


Её нет, совсем. Данный инструмент рассчитан на локальный запуск и работу в локальной сети с доверенными пользователями. Поскольку зачастую скрипты тоже пишутся для себя, они также не являются особо защищёнными. Соответственно прежде чем запускать такой сервер, нужно быть на 100% уверенным в фаерволле и пользователях.

С точки зрения скрытия данных, в Script server на текущий момент также ничего нет. Включать пароли в качестве параметров или в конфигурацию крайне не рекомендуется, поскольку они будут логироваться и храниться в чистом виде.

Примеры и скриншоты


Для отладки в проекте я завёл тестовые конфигурации, которые можно использовать для тестирования и демонстрации. В данном разделе все предоставленные скриншоты будут основываться на них.

Конфигурация скриптов


Относительно стандартный баш скрипт с параметрами, вводом пользователя во время работы и печатью: Write file.

Простой питон-скрипт, который выводит стену текста, разбивая его на абзацы и запрашивая input пользователя: Destroy world. Эта конфигурация используется для отладки пользовательского интерфейса при отображении параметров. Содержит все возможные типы параметров: Parameterized

Скриншоты


Полный экран
26cf843f4a6945dca2adb41fcf8aef67.png

Слева область выбора скриптов, справа информация по текущему открытому скрипту:

  • Название
  • Описание
  • Список параметров
  • Кнопки запуска/остановки
  • Output скрипта

Панель параметров
1b85b686cefd4909af18db710ecf8d2f.png

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

Панель input/output
374f86f9ac844043a2c0fb17cd7726cc.png

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

Дальнейшие шаги


На данный момент разработка не ведётся, т.к. в текущем виде Script server полностью покрывает возложенные на него задачи. Что-либо оптимизировать в текущем интерфейсе я также не вижу необходимости. Таким образом мы с ним оба находимся в ожидании новых вызовов и проблем.

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

» Ссылка на репозиторий проекта: github.com/bugy/script-server

Список используемых библиотек/фрейворков/благодарностей:

  • materializecss — materializecss.com
  • tornado — www.tornadoweb.org
  • Отдельное спасибо моему прежнему коллеге за поддержку и помощь в решении некоторых web проблем, особенно касаемых вёрстки.

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

Комментарии (3)

  • 8 сентября 2016 в 16:27

    0

    А не пробывали для этой задачи использовать rundeck?
  • 8 сентября 2016 в 16:31

    0

    Это что-то вроде lite-версии ansible?
    Достаточно использовать paramiko и сделать над ним обертку
    Хотя есть плюс, сделано симпатично, но функционал урезан
  • 8 сентября 2016 в 16:31

    0

    А чем плох Jenkins для таких задач?

© Habrahabr.ru