Skip to content

Instantly share code, notes, and snippets.

@Bazai
Last active March 17, 2016 12:35
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Bazai/534661806b449bdc68d9 to your computer and use it in GitHub Desktop.
Save Bazai/534661806b449bdc68d9 to your computer and use it in GitHub Desktop.
Автоматизация сборки bower пакета в эпоху Continuous Integration и Continuous Delivery

Автоматизация сборки bower пакета в эпоху Continuous Integration и Continuous Delivery

В чем проблема?

###Package Managers

Для любой потенциально полезной библиотеки, если она действительно полезна, наступает момент, когда хочется ее использовать чуть ли не в каждом проекте. Резонно возникает вопрос: "Как быстро подключить эту библиотеку в другом проекте?" А если другой проект находится на другой машине? А если проектов десяток? Об этом умные люди задумывались уже давно и нашли решение в создании системы управления пакетами. Существует множество таких систем для любых языков программирования: Bundler, npm, Bower и так далее.

Системы управления пакетами прекрасно решают вопрос быстрого подключения библиотеки. То есть, решают проблему со стороны конечного пользователя - клиента. Но дают ли они вспомогательные средства для другой стороны - автора библиотеки?

Типичный релиз новой версии библиотеки

Итак, вы разработчик библиотеки, на вашей локальной машине есть ее исходный код. Есть настроенная система сборки пакета. Но какие действия приходится совершать, для того, чтобы опубликовать конечную сборку? Я перечислю только действия, которые требуются для Bower:

  1. Запустить сборку
  2. Скопировать полученные файлы в отдельный репозиторий bower пакета
  3. Отредактировать файл bower.json - как минимум проставить новую версию сборки по спецификации semver
  4. Обновить README.md
  5. Запушить все в удаленный репозиторий. Чаще всего на Github
  6. Проставить новый tag. Такой же, как и в bower.json
  7. Запушить новый тэг в удаленный репозиторий

И вот так каждый раз!!!

Конечно же, умный и ленивый разработчик не будет делать все это вручную. Но, даже если все эти действия уже автоматизированы скриптом, запускать его руками надоест уже после второго раза. Лично я не хочу для одной библиотеки держать два репозитория, заниматься двойным учетом версий. "Я не хочу ничего решать, я хочу платье!", как говорится.

Я хочу:

  1. В одном единственном репозитории
  2. В котором все раскидано по множеству папок и файлов
  3. В котором все уже умеет собираться в один итоговый файл, например webpack'ом

Просто:

  1. Сделать новый коммит
  2. Добавить новый тэг
  3. Запушить все это на Github
  4. И пойти гулять

А пока я гуляю, bower сборка создаст себя самостоятельно и точно так же самостоятельно себя опубликует.

В этом и заключается главная проблема: как автоматизировать сборку bower пакета в эпоху CI и Continuous Delivery?

Описание решения

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

Я не просто так упомянул Continuous Integration. В FormStamp написаны тесты и они запускаются автоматически силами TravisCI. Это действительно отличное решение для open source проектов. Оно:

  1. Бесплатное
  2. Быстрое
  3. Умеет деплоить и запускать собственные скрипты в случае успешного запуска тестов

Именно умение запускать собственные скрипты и позволяет реализовать все то, чего так страстно хочется во вступлении. Запомним эту важную мысль. Но как она помогает решить основную проблему?

Вся эта сборка что-то напоминает

Если внимательно приглядеться, вся публикация пакета очень сильно напоминает процесс деплоя:

  1. Просто создать набор файлов
  2. Просто залить этот набор в строго определенное место
  3. Триггернуть событие о новых файлах

В случае с деплоем веб приложения строго определенным местом является папка на удаленной машине, в которую смотрит http и app сервер. Триггером является команда на рестарт app сервера. Например просто nginx reload или kill -USR2 unicorn.pid.

В случае с bower пакетом строго определенным местом является Github репозиторий. Триггером - push нового тэга. Все остальное на себя берет bower'овская машинерия.

Получается, нужно реализовать эти действия на Travis'е, ровно после того, как запуск тестов был успешно завершен. И это настолько популярный сценарий, что для огромного набора сервисов Travis'ом уже все реализовано и названо Continuous Deployment. В общих чертах все реализации сводятся к тем же трем действиям, которые были перечислены выше. Но для Bower'а заготовки, к сожалению, нет.

Собственный план

Ну что ж, тогда собственный план для реализации bower деплоя. Естественно, это план действий, которые будут автоматически выполняться на Travis'овских машинах:

  1. Получить исходники FormStamp с Github: это Travis делает без лишних инструкций, во время билда
  2. Создать набор файлов: это уже настроено, делается одной webpack командой $(npm bin)/webpack
  3. Залить этот набор в строго определенное место: то есть залить файлы в отдельный репозиторий bower-formstamp на Github. Здесь начинаются проблемы, потому что Github разрешает пушить в репозитории только их владельцам. Естественно, рандомная Travis'овская машина владельцем репозитория не является. Придется помучаться.
  4. Триггернуть событие о новых файлах: запушить новый тэг в тот же самый bower-formstamp репозиторий. Если проблема из предыдущего пункта уже была решена, то понадобится минимум действий. Единственная потенциальная проблема - как определить название тэга?

Получилось 4 пункта, но первый пункт выполняется самостоятельно и не требует никаких дополнительных действий, поэтому можно считать, что пункт отсутствует вообще. Поэтому остаются все те же самые 3 действия. Но дьявол, как всегда, в деталях.

Детали реализации

1. Создать набор файлов

Как уже было обозначено в плане - это уже настроено, делается одной webpack командой $(npm bin)/webpack. Вопрос только в том, как включить эту сборку на Travis? Во-первых, в файле конфигурации для Travis - .travis.yml есть блок для запуска тестов:

script:
- 'npm test'

Эта команда для npm в свою очередь описана в package.json:

  "scripts": {
    "test": "node_modules/karma/bin/karma start --single-run",

Подобным образом описываются любые команды на запуск тестов на Travis. Именно в блоке script. Документация не даст соврать.

Все действия, которые должны запуститься после успешных тестов, описываются в том же самом .travis.yml в блоке after_success. Например так:

after_success:
- '$(npm bin)/webpack'

Но поскольку понадобится явно более, чем одно действие, лучше сразу создать отдельный файл скрипта /script/release.sh. Тогда after_success будет выглядеть так:

after_success:
- './script/release.sh'

Итого важные блоки в .travis.yml:

script:
- 'npm test'

after_success:
- './script/release.sh'

Вызов же $(npm bin)/webpack переместится в release.sh. На этом проблема создания набора файлов полностью исчерпана.

2. Залить набор в строго определенное место

Напомню, что все сводится к заливке файлов в специальный репозиторий на Github. Проблема в том, что Github разрешает пушить в репозитории только их владельцам. А рандомная Travis'овская машина владельцем репозитория не является. Решение проблемы лежит в авторизации машины с помощью Github'овских учетных данных. И, строго говоря, есть 3 способа сделать это:

  1. Авторизоваться с помощью login и password существующего github-пользователя
  2. Авторизоваться с помощью Github Personal access token существующего пользователя
  3. Использовать Deploy keys привязанные к определенному репозиторию

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

  1. И пароли, и токены связываются с учетной записью определенного пользователя. Не репозитория, не организации. Именно пользователя, именно человека. Это значит, что как только пользователь поменял пароль; или решил подчистить свои токены ночью в пятницу во время алкогольного озарения; или обиделся и ушел из компании; или случайно удалил учетную запись - все существующие настройки авторизации для деплоя перестали работать.
  2. Проблема безопасности: Travis будет брать все данные для авторизации из Github репозитория. Я не уверен, что есть люди, готовые осознанно размещать данные своей личной учетной записи в публичном месте, тем более в открытом виде. Но, даже если попытаться стать таким человеком и разместить, например, access token в открытом виде - Github это злодеяние обнаружит, напишет гневное письмо владельцу репозитория и принудительно заменит токен на "липовый".

В любом случае, проблема безопасности будет актуальна для всех трех описанных способов авторизации. Но и авторизация по login/password, и по github personal token - это на все 146% гарантированный способ выстрелить себе в ногу. Поэтому, остается только путь с использованием Deploy Keys.

Deploy keys и их шифрование

Разработчики из Travis прекрасно понимают, что большая часть их потребителей - open source разработчики, которые не настолько безумны, чтобы показывать секретные данные в открытом виде. Поэтому Travis придумали способ шифровать данные, выкладывать их в публичные репозитории, а затем дешифровать их обратно только тогда, когда эти данные понадобятся на Travis'е. Конечно, механизмы шифрования разработчики не изобретали, они взяли уже существующие инструменты, сделали достаточно понятный способ для их использования, описали его в документации и теперь можно вкладывать любые потенциально опасные данные прямо в файл конфигурации .travis.yml. Кроме того, подобным образом можно зашифровать файлы.

План секьюрного межрепозиторного взаимодействия

Ну что ж, раз уж все возможности для сокрытия важных данных имееются, раз уж выбран способ с использованием Deploy Keys, тогда план действий для заливки набора файлов в специальный репозиторий для bower-formstamp становится очень прозрачным:

  1. Создать пару ssh ключей локально
  2. Залить публичный ключ в Deploy Keys для bower-formstamp на Github
  3. Зашифровать локально приватный ключ с помощью gem travis encrypt-file
  4. Поместить зашифрованный приватный ключ в formstamp
  5. Расшифровать приватный ключ во время билда на Travis
  6. Склонировать bower-formstamp на Travis
  7. Дополнить bower-formstamp на Travis и запушить новые данные обратно

1. Создание пары ключей

Выполняем в консоли: ssh-keygen -f ~/.ssh/travis_deploy_bower_formstamp

Во время создания ключа ОЧЕНЬ ВАЖНО не указывать passphrase

2. Заливка публичного ключа

Выполняем: cat ~/.ssh/travis_deploy_bower_formstamp.pub. Результат выдачи нужно будет вставить в настройках bower репозитория на Github:

1-bower-formstamp-settings 2-bower-formstamp-add-deploy-key

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

В Key вводим результат выдачи cat ~/.ssh/travis_deploy_bower_formstamp.pub

3. Шифровка приватного ключа

Предполагается, что gem travis уже установлен локально. Вся шифровка будет выполняться с его помощью. Кроме того, запускать все travis команды нужно находясь в локальной копии formstamp репозитория. Тогда travis без труда определит и свяжет URL проекта на Github и URL проекта на Travis.

Выполняем в консоли:

travis encrypt-file ~/.ssh/travis_deploy_bower_formstamp

Если все прошло правильно, то в результате в консоли будет показан текст с инструкциями следующего вида:

storing result as travis_deploy_bower_formstamp.enc
storing secure env variables for decryption

Please add the following to your build script (before_install stage in your .travis.yml, for instance):

    openssl aes-256-cbc -K $encrypted_733433ba94d5_key -iv $encrypted_733433ba94d5_iv -in travis_deploy_bower_formstamp.enc -out ~\/.ssh/travis_deploy_bower_formstamp -d

gem travis этим самым сделал 4 вещи:

  1. Создал файл travis_deploy_bower_formstamp.enc и поместил его в локальной копии formstamp репозитория. Это файл позже нужно будет переместить и не забыть сделать коммит.
  2. Создал переменную окружения encrypted_733433ba94d5_key на Travis. Эта переменная будет доступна во время каждого билда. В логах билда значение переменной показываться не будет, если только вы не сделаете echo $encrypted_733433ba94d5_key где-нибудь в скрипте явно.
  3. Создал переменную $encrypted_733433ba94d5_iv, аналогично encrypted_733433ba94d5_key
  4. Показал инструкцию, как можно будет расшифровать travis_deploy_bower_formstamp.enc во время билда на Travis. Стоит сохранить формулировку этой инструкции, она понадобится позже.

Можно для надежности проверить существование этих ключей в интерфейсе Travis: 3-travis-formstamp-env-variables

4. Поместить зашифрованный приватный ключ в основной репозиторий

Я предпочитаю поместить файл ключа поближе к файлу релиз скрипта. Поэтому выполняем в консоли:

mv travis_deploy_bower_formstamp.enc ./script/travis_deploy_bower_formstamp.enc
git add ./script/travis_deploy_bower_formstamp.enc
git commit -m 'Encrypted private deploy key for Travis'
git push origin master

5. Расшифровать приватный ключ во время билда на Travis

Работа с файлами ключей завершена. Вся дальнейшая работа будет вестись для файла /script/release.sh:

touch ~/.ssh/travis_deploy_bower_formstamp
openssl aes-256-cbc -K $encrypted_733433ba94d5_key -iv $encrypted_733433ba94d5_iv -in script/travis_deploy_bower_formstamp.enc -out ~/.ssh/travis_deploy_bower_formstamp -d
chmod 600 ~/.ssh/travis_deploy_bower_formstamp
echo -e "Host github.com\n  IdentityFile ~/.ssh/travis_deploy_bower_formstamp" > ~/.ssh/config
git config --global user.email "robot@health-samurai.io"
git config --global user.name "Travis CI Deployer"

Что здесь произошло? По пунктам:

  1. По инструкции, которая была показана gem travis после шифровки приватного ключа был расшифрован файл script/travis_deploy_bower_formstamp.enc с использованем тех самых секретных Environment Variables на Travis. Расшифрованный приватный ключ теперь располагается в ~/.ssh/travis_deploy_bower_formstamp
  2. Файлу ~/.ssh/travis_deploy_bower_formstamp проставлены права на чтение и запись только владельцем. При всех других правах ssh будет ругаться в процессе клонирования git репозитория.
  3. В конфиг ssh была прописана инструкция использовать приватный ключ ~/.ssh/travis_deploy_bower_formstamp при доступе ко всем данным на github.com. В основном это будет относиться ко всем попыткам получить доступ к приватным репозиториям и ко всем попыткам git push
  4. Прописаны имя и емейл в git конфигурацию, чтобы в будущем легко было опознать коммиты и релизы, сделанные автоматически через Travis

6 и 7. Склонировать, изменить и запушить новые данные в bower репозиторий

/script/release.sh

# Clone
cd ..
git clone --depth=50 --branch=master git@github.com:formstamp/bower-formstamp.git
cd formstamp

# Build
$(npm bin)/webpack

# Change files
cp dist/formstamp.css ../bower-formstamp/
cp dist/formstamp.js  ../bower-formstamp/
cp bower.json         ../bower-formstamp/
cp README.md          ../bower-formstamp/
cp CHANGELOG.md       ../bower-formstamp/

# Push new files
cd ../bower-formstamp
git add .
git commit -m "release at `date` by `git config --get user.name`"
git push origin master

Произошло следующее:

  1. По-умолчанию Travis клонирует базовый проект в /home/travis/<project_name>, в данном случае в /home/travis/formstamp. Поэтому рабочая папка изменена на /home/travis и bower-formstamp склонирован в нее. Для того, чтобы папки formstamp и bower-formstamp располагались на одном уровне.
  2. В папке formstamp сделан webpack билд проекта и все необходимые файлы скопированы напрямую в bower-formstamp. В данном случае плоско, без использования подпапок. Хотя часть файлов в formstamp располагалась в своих подпапках.
  3. Рабочая папка изменена на bower-formstamp. Поскольку были скопированы измененые файлы, сделан git commit. С помощью расшифрованного приватного ssh ключа изменения запушены на Github.

3. Триггернуть событие

На данном этапе есть полностью работающий механизм управления удаленным репозиторием bower-formstamp с Travis. Все новые файлы были залиты и закоммичены. Остается только установить триггер для Bower машинерии. Напомню, что триггер - это просто git tag. Поэтому остается дополнить /script/release.sh дополнительным пушем:

git tag -a -m "new-semver-tag" new-semver-tag
git push --tags

Только подобная реализация будет каждый раз использовать одно и то же название тэга - new-semver-tag и Bower никогда не обнаружит изменений. Пора вспомнить о базовой идее: добавить и запушить новый тэг в основном репозитории проекта (formstamp). Travis должен опознать новый тэг и только при его наличии запустить релизный скрипт.

Для этого есть огромное количество переменных окружения Travis, доступных только во время билда на Travis. Переменная TRAVIS_TAG отлично соответствует всей базовой задумке. Остается задействовать ее в трех местах:

  1. В месте вызова релиз скрипта в .travis.yml
  2. В манифест файле Bower: bower.json, в блоке "version": "AUTO_VERSION". Предполагается, что номер версии всегда будет проставляться при автоматическом билде, поэтому bower.json в formstamp будет просто шаблоном, в котором номер версии никогда не будет корректироваться вручную. Таким образом Нужно будет автозаменить AUTO_VERSION на значение TRAVIS_TAG.
  3. В установке тэга в конце /script/release.sh

Теперь они будут выглядеть так:

1. .travis.yml:

...
after_success:
- 'if [ -n "$TRAVIS_TAG" ]; then ./script/release.sh; fi'
...

2. /script/release.sh:

...
# Замена AUTO_VERSION в bower.json
sed -i.bak "s/AUTO_VERSION/$TRAVIS_TAG/g" bower.json && rm bower.json.bak
...

3. /script/release.sh:

...
# Добавление нового тэга
git tag -a -m "$TRAVIS_TAG" $TRAVIS_TAG
...

И все! Теперь все работает само.

Итого

После того, как все нюансы оговорены, не составит никакого труда просто взглянуть на исходный код файлов, задействованных в статье, в репозитории FormStamp и понять все сразу. Файлы на Github'е:

Ну а пока вы все разглядываете, я просто последую своему базовому плану:

  1. Коммит
  2. Тэг
  3. Пуш
  4. Пошел гулять

Спасибо за внимание.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment