Среда разработки: делаем рабочие процессы удобнее
Vagrant
Для удобного процесса разработки, быстрого переключения между проектами и эффективного взаимодействия бекэнд и фронтенд команд мы в WB—Tech работаем в виртуальном окружении Vagrant и VirtualBox.
Vagrant — кросс-платформенное ПО для создания виртуальной среды разработки. Для ускорения развертывания виртуальной машины можно использовать компилированные, версированные боксы. Версийность боксов в Vagrant описывается при помощи JSON документа.
{
"name": "box_name",
"description": "This box description.",
"versions": [
{
"version": "42.0",
"providers": [
{
"name": "virtualbox",
"url": "http://somewhere.com/precise64_010_virtualbox.box",
"checksum_type": "sha1",
"checksum": "foo"
}
]
}
]
}
В самом Vagrantfile указать путь к метаданным в атрибуте config.vm.box_url.
config.vm.box = "box_name"
config.vm.box_version = "42.0"
config.vm.box_url = "http://somewhere.com/path/to/metadata.json"
Лайфхак: при обновлении версии бокса мы используем Nginx с дополнительным модулем, потому что описывать документ каждый раз вручную не практично. Формирование метаданных сделано при помощи простого скрипта на Lua. Мы хостим боксы самостоятельно с помощью Lua.
Почему именно Lua
Lua — скриптовый язык программирования. По возможностям, идеологии и реализации язык ближе всего к JavaScript, однако отличается более мощными и гибкими конструкциями.
Несмотря на то что Lua не содержит понятия класса и объекта в явном виде, механизмы ООП, в том числе множественное наследование, легко реализуются с использованием метатаблиц. Эти таблицы также отвечают за перегрузку операций и прочее.
Реализуемая модель ООП — прототипная (как и в JavaScript). Интерпретатор языка — свободно распространяемый с открытыми исходными текстами на языке C.
Установка и зависимости
Описание приведено для операционных систем семейства Debian.
- Прежде всего нужен Nginx с модулем lua-nginx-module. Установите готовый пакет nginx-extras, либо соберите вручную.
- Потребуется интерпретатор Lua, чтобы протестировать скрипт в интерактивной консоли.
- Для компиляции модулей потребуется утилита make.
- Менеджер пакетов luarocks: для поиска файлов в директории используем модуль luaposix, для конвертации словаря в JSON — JSON4Lua.
$ sudo apt-get -y install make nginx-extras lua5.1 luarocks
$ # install lua modules
$ sudo luarocks install luaposix
$ sudo luarocks install JSON4Lua
«Здравствуй, подлунный мир!»
Фраза “Hello world!” на Lua так же проста, как и на Python.
Lua 5.1.5 Copyright (C) 1994-2012 Lua.org, PUC-Rio
> print "Hello world!"
Hello world!
Теперь попробуем тоже самое при помощи полнофункционального Nginx Lua API.
server {
listen 80;
location /hello-world {
content_by_lua '
ngx.header.content_type = "text/plain"
ngx.say("Hello world!")
';
}
}
$ curl http://10.1.1.111/hello-world
Hello world!
Для исполнения скрипта служит директива content_by_lua, для которого Nginx получает ответ через API. Если скрипт большой, не обязательно описывать его внутри конфигурации, можно подключить через директиву content_by_lua_file.
Vagrant метаданные
Настройка Nginx
На сервере мы складываем боксы в директорию hosted, создавая поддиректорию для каждого проекта. Сами боксы со строго указанным форматом имени {provider}-{version.subversion}.box.
Формируется такое дерево.
$ tree hosted/
hosted/
├── foo
│ ├── docker-1.0.box
│ ├── docker-1.3.box
│ ├── virtualbox-1.0.box
│ ├── virtualbox-1.4.box
│ └── virtualbox-1.7.box
└── bar
├── virtualbox-1.0.box
├── virtualbox-1.1.box
└── virtualbox-1.2.box
2 directories, 8 files
Настроим Nginx так, чтобы для любого бокса начиналось скачивание, а для имени проекта возвращались вычисленные метаданные.
server {
listen 80;
set $box_url 'http://10.1.1.111/%s/%s-%s.box';
set $box_prefix '/home/vagrant/proj/hosted/';
location ~ /*\.box$ {
root /home/vagrant/proj/hosted;
# just return box
}
location ~ /(?<box_name>\w+)/?$ {
content_by_lua_file /home/vagrant/proj/app/handler.lua;
}
}
Переменные $box_url и $box_prefix будут использоваться при формировании метаданных.
Вычисление метаданных с помощью Lua
Теперь сформируем метаданные для версирования Vagrant боксов. Идея в том, что по запросу Lua будет осуществлять поиск сохраненных боксов в заданной директории на сервере, вычислять их хеш-суммы и создавать ответ в формате метаданных Vagrant.
Используя glob из библиотеки posix, найдем все боксы.
local box_root = ngx.var.box_prefix .. ngx.var.box_name .. '/'
local posix = require "posix"
local glob = posix.glob (box_root .. '*.box')
-- Если боксы не найдены, можно сразу возвращать 404
if not glob then
ngx.status = ngx.HTTP_NOT_FOUND
return ngx.exit (ngx.HTTP_NOT_FOUND)
end
Итерациями пройдем по найденным боксам и сформируем словарь с найденными версиями.
local versions = {}
-- Discover the boxes
for _, box in ipairs (glob) do
-- Обрабатываем найденый бокс, определяя версию и формируя описание
local provider, version = make_provider (box)
if version then
if versions[version] == nil then
-- Если версия встречается впервые, создаем запись для новой версии
versions[version] = {
version = version,
providers = {provider}
}
else
-- Если версия уже была описана, обновляем список провайдеров
table.insert (versions[version]['providers'], provider)
end
end
end
Для вычисления хеш-суммы больших файлов боксов используем утилиты
OC — sha1sum, sha256sum, md5sum с помощью вызова процесса через io.popen.
local hash = 'sha1'
function get_hash (filepath)
-- Вычисляем хешсумму используя вызов консольной утилиты sha1sum
local command = string.format ('%ssum %s | cut -d " " -f1', hash, filepath)
local hashsum = assert (io.popen (command, 'r'))
local result = string.gsub (hashsum:read ('*a'), '\n', '')
hashsum:close ()
return result
end
Функция make_provider выполняется для каждого найденного бокса. Подразумевается, что боксы хранятся на сервере со строго заданным форматом имени: {provider}-{version.subversion}.box
Разбираем версию и имя провайдера, после чего формируем словарь, описывающий данный бокс.
local function make_provider (filepath)
-- Make vagrant provider from given file
local box_provider, box_version = string.match (
filepath, string.format ('%s(%%a+)-(.+).box', box_root))
return {
-- Название провайдера virtualbox или docker
name = box_provider,
-- Прямая ссылка на бокс, которую будет запрашивать vagrant
url = string.format (ngx.var.box_url, ngx.var.box_name, box_provider, box_version),
-- Алгоритм хешсуммы sha1, sha256, md5
checksum_type = hash,
-- Строка со значением хешсуммы
checksum = get_hash(filepath)
}, box_version
end
Обработав все боксы и сформировав список версий, обернем все в дополнительный словарь.
-- Make result response
local vagrant = {
name = ngx.var.box_name,
description = string.format ("Boxes for %s proj", ngx.var.box_name),
versions = {}
}
for _, version in pairs (versions) do
table.insert (vagrant['versions'], version)
end
Ответ сервера JSON с найденными версиями.
ngx.header.content_type = "application/json; charset=utf-8"
local json = require "json"
ngx.say (json.encode (vagrant))
Полученный скрипт формирует JSON ответ c метаинформацией о боксах.
$ curl http://10.1.1.111/example | jq
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 943 0 943 0 0 13412 0 --:--:-- --:--:-- --:--:-- 13471
{
"versions": [
{
"version": "1.7",
"providers": [
{
"url": "http://10.1.1.111/example/virtualbox-1.7.box",
"checksum": "3221c0fd58a4b2430efc5eeaf09cb8eaf877f3a9",
"name": "virtualbox",
"checksum_type": "sha1"
}
]
},
{
"version": "1.3",
"providers": [
{
"url": "http://10.1.1.111/example/docker-1.3.box",
"checksum": "def7148aa7ded879dbf5944af4785c2b09aba97a",
"name": "docker",
"checksum_type": "sha1"
}
]
},
{
"version": "1.4",
"providers": [
{
"url": "http://10.1.1.111/example/virtualbox-1.4.box",
"checksum": "63b06d8c065f5c2522c356d4d6ceb718ec3f8198",
"name": "virtualbox",
"checksum_type": "sha1"
}
]
},
{
"version": "1.0",
"providers": [
{
"url": "http://10.1.1.111/example/docker-1.0.box",
"checksum": "65cb550765d251604dcfeedc36ea61f66ce205c4",
"name": "docker",
"checksum_type": "sha1"
},
{
"url": "http://10.1.1.111/example/virtualbox-1.0.box",
"checksum": "c0a9d5c3d6679cfcc4b1374e3ad42465f3dd596e",
"name": "virtualbox",
"checksum_type": "sha1"
}
]
}
],
"name": "example",
"description": "Boxes for example proj"
}
Полный пример скрипта можно посмотреть в репозитории на Github. И при желании поиграться, запустив настроенный Vagrant.
Другие статьи серии о о программных решениях, которые мы используем в своей работе:
- Плагин ClickZoomer
- Плагин Rotator
- Плагин Comparator
- Быстрый старт на Django
- Настроить VPN
Если хотите убедиться, что все делаете правильно или проконсультироваться по поводу разработки вашего проекта, напишите нам.
Подпишитесь на блог WB—Tech
Никакого спама, только анонсы новых статей!