Для удобного процесса разработки, быстрого переключения между проектами и эффективного взаимодействия бекэнд и фронтенд команд мы в 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 — скриптовый язык программирования. По возможностям, идеологии и реализации язык ближе всего к JavaScript, однако отличается более мощными и гибкими конструкциями.
Несмотря на то что Lua не содержит понятия класса и объекта в явном виде, механизмы ООП, в том числе множественное наследование, легко реализуются с использованием метатаблиц. Эти таблицы также отвечают за перегрузку операций и прочее.
Реализуемая модель ООП — прототипная (как и в JavaScript). Интерпретатор языка — свободно распространяемый с открытыми исходными текстами на языке C.
Описание приведено для операционных систем семейства Debian.
$ 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-worldHello world!
Для исполнения скрипта служит директива content_by_lua, для которого Nginx получает ответ через API. Если скрипт большой, не обязательно описывать его внутри конфигурации, можно подключить через директиву content_by_lua_file.
На сервере мы складываем боксы в директорию 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.box2 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 будут использоваться при формировании метаданных.
Теперь сформируем метаданные для версирования 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 boxesfor _, 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 endend
Для вычисления хеш-суммы больших файлов боксов используем утилиты
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 resultend
Функция 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_versionend
Обработав все боксы и сформировав список версий, обернем все в дополнительный словарь.
--Make result responselocal 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
{
"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.
Если хотите убедиться, что все делаете правильно или проконсультироваться по поводу разработки вашего проекта, напишите нам.
Никакого спама, только анонсы новых статей
ИП Гришанин Кирилл Олегович
ИНН 774313842609
Б. Новодмитровская ул., 36, стр. 12, вход 6,
Москва, Россия, 127015
Ahad Ha'am 54,Tel Aviv-Yafo,Израиль