Когда разработка мобильных приложений в компании переходит на промышленные рельсы, неизменно всплывает вопрос об автоматической сборке. Continuous Integration — это то, что является неотъемлемой частью процесса. Итог этого процесса — сборки для тестирования на устройствах компании для OTA распространения заказчикам и удаленным тестировщикам.
При публикации под аккаунтом своей компании или под аккаунтом заказчика приходится иметь дело с большим количеством профилей и сертификатов, ключей, свойств и настроек. Не всегда удобно заменять конкретный ключ на устройстве. Поэтому в нашей компании мы придумали решение, автоматизирующее этот процесс. Автоматизация распространения артефактов на всех устройствах, используемых для сборки, и будет описана в этой статье.
Итак, эта статья о том, как реализовать копирование сертификатов и профилей для iOS сборок и свойств, и ключей для Android сборок на все слейвы дженкинса в рамках Continuous Integration. Такая автоматизация экономит время и помогает избежать ошибок. Статья будет интересна разработчикам мобильных приложений и тем, кто администрирует Jenkins.
Поехали!
Несколько лет назад, когда мы только настроили Jenkins (сервер непрерывной интеграции), у нас была всего одна машинка (мак-мини), на которой выполнялись все сборки. Со временем дженкинс переехал на виртуалку, а миник превратился в слейва (вспомогательный узел дженкинса, использующийся для сборки проектов). iOS-проекты можно собирать только на Mac OS, а Android — везде, поэтому iOS-ные проекты собирались на слейве, а Androidные — на мастере (где установлен сам дженкинс). Всё, что необходимо для сборки проектов, устанавливалось и раскладывалось на двух машинах руками, и всё было ок! О том, как настроить Jenkins, мы рассказали в одной из предыдущих статей — «Автоматическая сборка iOS-приложений на разных версиях Xcode с помощью Jenkins».
Количество проектов росло, и мы подключили сначала по одному сборщику для iOS и Android проектов, а позже — больше. Чтобы собирать приложения на свежеподключенных слейвах, необходимо иметь установленные инструменты, SDK, и для каждого проекта — ключи и файлы, которыми приложение подписывается.
Мы решили оптимизировать настройку слейвов дженкинса, а именно сделать автоматическое копирование и установку необходимых файлов для подписывания iOS и Android приложений.
Есть несколько слейвов дженкинса для сборки iOS проектов. Все слейвы помечены лейблами, позволяющими определить версию Xcode, установленную на них. Для распределения нагрузки все сборки помечены лейблами, и могут быть выполнены на любом слейве с соответствующим Xcode.
Для того, чтобы собрать iOS приложение, необходимо иметь:
Обычно на jenkins мы собираем Distribution сборки — AdHoc, Enterprise и AppStore. Вот только собирать приходится для большого числа аккаунтов разработчика, соответственно, и сертификатов с ключами у нас довольно много.
Чтобы положить сертификат с ключом на удаленную машинку, нужно:
Чтобы сделать то же самое через ssh, нужно использовать команду security.
Для установки профилей достаточно скопировать их в нужную директорию: ~/Library/MobileDevice/Provisioning\ Profiles/
При этом старый профиль желательно удалить, т.к. при сборке Xcode может использовать старый профиль вместо обновленного.
Обычно для обновления списка профилей достаточно зайти в настройки Xcode и, выбрав нужный аккаунт, нажать кнопку Reload. Xcode сам заменит старые профили на новые, если они были отредактированы на портале. Но проделывать это на нескольких машинах под всеми аккаунтами — не эффективно и вообще уныло.
Можно настроить синхронизацию папок, например, rsync'ом, и обновлять профили руками только на одной машине. Мы выбрали немного другой подход, потому что доступ к редактированию и созданию сертификатов и профилей есть у нескольких человек, и каждый может работать как на виртуалке, так и на своей машине: мы сделали гит-репозиторий для сертификатов, ключей и профилей.
Дальше осталось немного — по обновлению в гите запускать скрипт, который разложит файлы по нодам дженкинса и установит сертификаты и ключи. Первая мысль была — написать git-hook, но у гитовой машины нет доступа к нодам дженкинса. А вот у дженкинса он есть. И дженкинс отлично выполняет job'ы, которые стартуют, когда происходит пуш в гит — для этого в проекте в гите добавляется web hook.
В результате мы сделали:
С Android-проектами ситуация в целом похожая. Только вместо профилей и сертификатов для подписывания сборок используются файлы keystore. Для использования файла keystore необходимо знать пароль и алиас. Для сборок Android-приложений мы используем Gradle и в файле настроек gradle.properties указываем следующую информацию:
В простой ситуации можно положить файл keystore на машине-сборщике и залить в гит .properties проекта. Сложности начинаются, когда разработчики и сборщики работают на разных операционных системах: сложно положить ключи на всех машинках по одинаковому пути. Наше решение для Android-сборок:
Сборка, которая раскладывает файлы по слейвам, выполняется всегда на мастере, т.к. у мастера есть доступ ко всем сборщикам. Для того, чтобы указать правильные пути для различных операционных систем, в гит файлы кладутся с плейсхолдером «PATH_TO_KEYS», т.е. в нашем случае файл выглядит так:
Нужные пути в файлах подставляются во время исполнения сборки с помощью команды sed. Чтобы использовать файлы, скачанные из репозитория, несколько раз, перед изменением путей, они копируются в отдельную папку. В нашем примере на слейвах home directory совпадает, но отличается от home directory мастера.
В настройках проекта для релизной сборки путь к gradle.properties указан относительно директории проекта, поэтому файл properties нужно скопировать из общей директории сборщика ~/gradle_keys в рабочую директорию, для этого в сборке проекта перед Invoke Gradle script выставляем Build step Execute shell:
Чтобы не выписывать адреса всех слейвов, можно использовать плагин Elastic+Axis, который позволяет настроить сборку для выполнения на нескольких узлах. Скрипт для настройки сборщиков iOS будет выглядеть так:
Скрипт для настройки сборщиков Android будет выглядеть так:
Говорят, если хочешь написать хорошую статью, нужно написать выводы о сильных и слабых сторонах своего решения. С сильными сторонами тут все просто:
Слабые стороны:
Предложенный подход позволил в нашей компании упростить процедуру управления многочисленными артефактами, необходимыми для сборки приложений. Теперь изменение и добавление ключей происходит быстро, стабильно и на всех нужных машинах. При расширении инфраструктуры из-за увеличения числа проектов добавление новых узлов для дженкинса происходит так же быстро и легко. Мы рады поделиться опытом и будем рады, если эта статья поможет решить подобную проблему в тех компаниях, где накопилось большое количество разрабатываемых и поддерживаемых приложений.
При публикации под аккаунтом своей компании или под аккаунтом заказчика приходится иметь дело с большим количеством профилей и сертификатов, ключей, свойств и настроек. Не всегда удобно заменять конкретный ключ на устройстве. Поэтому в нашей компании мы придумали решение, автоматизирующее этот процесс. Автоматизация распространения артефактов на всех устройствах, используемых для сборки, и будет описана в этой статье.
Итак, эта статья о том, как реализовать копирование сертификатов и профилей для iOS сборок и свойств, и ключей для Android сборок на все слейвы дженкинса в рамках Continuous Integration. Такая автоматизация экономит время и помогает избежать ошибок. Статья будет интересна разработчикам мобильных приложений и тем, кто администрирует Jenkins.
Поехали!
Проблема
Несколько лет назад, когда мы только настроили Jenkins (сервер непрерывной интеграции), у нас была всего одна машинка (мак-мини), на которой выполнялись все сборки. Со временем дженкинс переехал на виртуалку, а миник превратился в слейва (вспомогательный узел дженкинса, использующийся для сборки проектов). iOS-проекты можно собирать только на Mac OS, а Android — везде, поэтому iOS-ные проекты собирались на слейве, а Androidные — на мастере (где установлен сам дженкинс). Всё, что необходимо для сборки проектов, устанавливалось и раскладывалось на двух машинах руками, и всё было ок! О том, как настроить Jenkins, мы рассказали в одной из предыдущих статей — «Автоматическая сборка iOS-приложений на разных версиях Xcode с помощью Jenkins».
Количество проектов росло, и мы подключили сначала по одному сборщику для iOS и Android проектов, а позже — больше. Чтобы собирать приложения на свежеподключенных слейвах, необходимо иметь установленные инструменты, SDK, и для каждого проекта — ключи и файлы, которыми приложение подписывается.
Мы решили оптимизировать настройку слейвов дженкинса, а именно сделать автоматическое копирование и установку необходимых файлов для подписывания iOS и Android приложений.
Начнем с iOS
Есть несколько слейвов дженкинса для сборки iOS проектов. Все слейвы помечены лейблами, позволяющими определить версию Xcode, установленную на них. Для распределения нагрузки все сборки помечены лейблами, и могут быть выполнены на любом слейве с соответствующим Xcode.
Для того, чтобы собрать iOS приложение, необходимо иметь:
- Xcode
- Developer/Distribution certificate + Private key
- Provisioning profile
Обычно на jenkins мы собираем Distribution сборки — AdHoc, Enterprise и AppStore. Вот только собирать приходится для большого числа аккаунтов разработчика, соответственно, и сертификатов с ключами у нас довольно много.
Чтобы положить сертификат с ключом на удаленную машинку, нужно:
- экспортировать сертификат с ключом на машине, на которой они установлены
- скопировать файл .p12 на удаленную машину
- зайти на удаленную машину и открыть файл .p12, ввести пароль для установки сертификата и ключа в кейчейн,
- после установки сертификата и ключа открыть информацию о ключе и во вкладке Access Control выбрать «Allow all applications to access this item»
Чтобы сделать то же самое через ssh, нужно использовать команду security.
Для установки профилей достаточно скопировать их в нужную директорию: ~/Library/MobileDevice/Provisioning\ Profiles/
При этом старый профиль желательно удалить, т.к. при сборке Xcode может использовать старый профиль вместо обновленного.
Обычно для обновления списка профилей достаточно зайти в настройки Xcode и, выбрав нужный аккаунт, нажать кнопку Reload. Xcode сам заменит старые профили на новые, если они были отредактированы на портале. Но проделывать это на нескольких машинах под всеми аккаунтами — не эффективно и вообще уныло.
Можно настроить синхронизацию папок, например, rsync'ом, и обновлять профили руками только на одной машине. Мы выбрали немного другой подход, потому что доступ к редактированию и созданию сертификатов и профилей есть у нескольких человек, и каждый может работать как на виртуалке, так и на своей машине: мы сделали гит-репозиторий для сертификатов, ключей и профилей.
Дальше осталось немного — по обновлению в гите запускать скрипт, который разложит файлы по нодам дженкинса и установит сертификаты и ключи. Первая мысль была — написать git-hook, но у гитовой машины нет доступа к нодам дженкинса. А вот у дженкинса он есть. И дженкинс отлично выполняет job'ы, которые стартуют, когда происходит пуш в гит — для этого в проекте в гите добавляется web hook.
В результате мы сделали:
- гит-репозиторий, в котором в папках profiles и certificates лежат *.mobileprovision и *.p12
- job в дженкинсе, который берет файлы из гита и выполняет скрипт:
#!/bin/bash slaves=( "192.168.2.10" "192.168.2.20" "192.168.2.30" ) for i in "${slaves[@]}" do ssh jenkins@"$i" rm -f "~/Library/MobileDevice/Provisioning\ Profiles/*" scp ${WORKSPACE}/profiles/* "jenkins@$i:/Users/jenkins/Library/MobileDevice/Provisioning\ Profiles/" ssh jenkins@"$i" mkdir -p "~/pp_cc" scp ${WORKSPACE}/certificates/* "jenkins@$i:/Users/jenkins/pp_cc/" ssh jenkins@"$i" 'security unlock-keychain -p jenkins /Users/jenkins/Library/Keychains/login.keychain; cd /Users/jenkins/pp_cc/; for a in *; do security import $a -k /Users/jenkins/Library/Keychains/login.keychain -P xxxXXXxxx -A; done' done
slaves — это массив слейвов дженкинса, а точнее их ip-адресов. xxxXXXxxx — пароль от p12. Да, мы для всех сертификатов, которые кладем в гит, используем одинаковый пароль.
И такая же магия для Android
С Android-проектами ситуация в целом похожая. Только вместо профилей и сертификатов для подписывания сборок используются файлы keystore. Для использования файла keystore необходимо знать пароль и алиас. Для сборок Android-приложений мы используем Gradle и в файле настроек gradle.properties указываем следующую информацию:
releaseStoreFile=/home/jenkins/gradle_keys/customer/project_name.keystore
releaseStorePassword=project_pass
releaseKeyAlias=project_alias
releaseKeyPassword=project_pass
В простой ситуации можно положить файл keystore на машине-сборщике и залить в гит .properties проекта. Сложности начинаются, когда разработчики и сборщики работают на разных операционных системах: сложно положить ключи на всех машинках по одинаковому пути. Наше решение для Android-сборок:
- гит-репозиторий для keystore и properties
- сборка на дженкинсе, которая выполняет скрипт:
# slaves cp -r keys keys_for_ubuntu cd keys_for_ubuntu PATHREPL='\/home\/jenkins\/gradle_keys' find . -name "*\.properties" | while read line; do sed -i -e 's/'PATH_TO_KEYS'/'"$PATHREPL"'/g' "$line"; done slaves=( "192.168.2.10" "192.168.2.20" "192.168.2.30" ) for i in "${slaves[@]}" do scp -r ./ jenkins@"$i":/home/jenkins/gradle_keys/ done #master cd ../keys PATHREPL='\/var\/lib\/jenkins\/gradle_keys' find . -name "*\.properties" | while read line; do sed -i -e 's/'PATH_TO_KEYS'/'"$PATHREPL"'/g' "$line"; done cp -r ./ /var/lib/jenkins/gradle_keys/
Сборка, которая раскладывает файлы по слейвам, выполняется всегда на мастере, т.к. у мастера есть доступ ко всем сборщикам. Для того, чтобы указать правильные пути для различных операционных систем, в гит файлы кладутся с плейсхолдером «PATH_TO_KEYS», т.е. в нашем случае файл выглядит так:
releaseStoreFile=PATH_TO_KEYS/customer/project_name.keystore
releaseStorePassword=project_pass
releaseKeyAlias=project_alias
releaseKeyPassword=project_pass
Нужные пути в файлах подставляются во время исполнения сборки с помощью команды sed. Чтобы использовать файлы, скачанные из репозитория, несколько раз, перед изменением путей, они копируются в отдельную папку. В нашем примере на слейвах home directory совпадает, но отличается от home directory мастера.
В настройках проекта для релизной сборки путь к gradle.properties указан относительно директории проекта, поэтому файл properties нужно скопировать из общей директории сборщика ~/gradle_keys в рабочую директорию, для этого в сборке проекта перед Invoke Gradle script выставляем Build step Execute shell:
cp ~/gradle_keys/customer/project_name.properties ${WORKSPACE}/
Как сделать еще лучше
Чтобы не выписывать адреса всех слейвов, можно использовать плагин Elastic+Axis, который позволяет настроить сборку для выполнения на нескольких узлах. Скрипт для настройки сборщиков iOS будет выглядеть так:
rm -f ~/Library/MobileDevice/Provisioning\ Profiles/*
cp ${WORKSPACE}/profiles/* ~/Library/MobileDevice/Provisioning\ Profiles/
security unlock-keychain -p jenkins $HOME/Library/Keychains/login.keychain; cd ${WORKSPACE}/certificates/; for a in *; do security import $a -k $HOME/Library/Keychains/login.keychain -P xxxXXXxxx -A; done
Скрипт для настройки сборщиков Android будет выглядеть так:
cd keys
KEYS_PATH="$HOME/gradle_keys"
REPL_PATH=$(echo "$KEYS_PATH" | sed -e 's/[\/&]/\\&/g')
find . -name "*\.properties" | while read line; do sed -i -e "s/PATH_TO_KEYS/$REPL_PATH/g" "$line"; done
cp -r ./ $KEYS_PATH
Выводы
Говорят, если хочешь написать хорошую статью, нужно написать выводы о сильных и слабых сторонах своего решения. С сильными сторонами тут все просто:
- это решение экономит время при изменении и добавлении файлов для сборок
- это решение экономит время на добавление слейвов при расширении инфраструктуры
- это решение позволяет ограничить доступ к важным файлам (ключи и сертификаты не лежат в репозитории проекта) — у нас они и раньше не лежали, но говорят, что некоторые так делают
- это решение — прикольное, потому что дженкинс «настраивает» сам себя
Слабые стороны:
- это решение рассчитано на то, что один репозиторий используется для ключей от всех Android-проектов и аналогично с iOS. Соответственно, настройка доступа к нему негибкая — у человека либо есть доступ ко всему, либо нет доступа ни к чему. В случае, если доступ к подобным файлам должен иметь не главный технический специалист по направлению, а, например, менеджер проекта, это решение не подойдет.
- безопасность этого подхода во многом зависит от безопасности реализации идеи релизных сборок на дженкинсе вообще, т.е., если вы их используете, вам все равно нужно где-то хранить ключи, прописывать пути к ним и т.п., и безопасность зависит от настройки доступа к задачам дженкинса, к машинам, на которых установлены дженкинс и его сборщики, от способа распространения собранных приложений.
Заключение
Предложенный подход позволил в нашей компании упростить процедуру управления многочисленными артефактами, необходимыми для сборки приложений. Теперь изменение и добавление ключей происходит быстро, стабильно и на всех нужных машинах. При расширении инфраструктуры из-за увеличения числа проектов добавление новых узлов для дженкинса происходит так же быстро и легко. Мы рады поделиться опытом и будем рады, если эта статья поможет решить подобную проблему в тех компаниях, где накопилось большое количество разрабатываемых и поддерживаемых приложений.
And1ty
Для iOS еще очень удобно использовать утилиты из пакета fastlane:
sigh для управления сертификатами — github.com/KrauseFx/sigh
и cert для provisioning profiles — github.com/fastlane/cert
ulechka
Спасибо!