git checkout develop
git merge bug_fix_#999
git checkout master && git merge develop --no-ff ....
git push ....
На пуш на сервере срабатывает jenkins/teamcity/travis, который запускает билд. В это же самое время наш Боб пишет Алисе, что скоро пойдет домой, и хочет, чтобы аппа ушла сегодня в app store на апрув, дабы выиграть лишние пару дней, так как на носу выходные, если, конечно, приложение пройдет ручное тестирование Алисы.
Приложение Боба довольно обычное: пару сотен компилируемых класс файлов, еще с десяток cocoapods зависимостей ну и кучка сторибордов — Боб ценит своё время и время коллег поэтому не пишет UI в коде. Боб знает, что его приложение с чистого старта на сервере собирается за 4 минуты для develop версии, которое идет на тест Алисе, и столько же или чуть больше для production версии. Боб также знает, что ему нужно около 10 минут, чтобы дождаться окончания полной сборки и затем сообщить Алисе, что она может приступать к тестированию. Боб человек ответственный, поэтому по истечении 10 минут после пуша проверяет статус билда, так как знает, что сервер — это отдельный параллельный мир со своими правилами, законами и странностями.
Пятница, вечер, Боба отделяет от долгожданных выходных только 10 минут, после которых передаст эстафету Алисе. Боб вбивает в сафари bobcompany.ci/dashboard, где видит красную лампочку напротив своего приложения, глаза Боба потускнели, разочарованию не было предела. Боб жмет на show more, где его встречает ошибка:
Code Sign error: No codesigning identities found: No codesigning identities (i.e. certificate and private key pairs) that match the provisioning profile specified in your build settings (“com.company.bob”) were found.
Тут нервы Боба совсем сдают:
*Кратко об ошибке, она проявляется, когда мы пытаемся подписать приложение несуществующим сертификатом, под несуществующим понимается или он не установлен на машине, или он устарел и mobileprovision заведен на более свежую версию сертификата того же аккаунта для того же бандла.
И самое обидное, что эта ошибка только для production версии билда, который запускается вторым после develop версии, причем, сначала xcode компилирует зависимости (cocoapods) и уже только после проверяет валидность подписи, то есть только когда собирает основное приложение. Поэтому ошибка проявляется примерно во второй половине процесса сборки, из-за чего первые 6-7 минут потрачены в пустую, от чего Боб расстроен еще больше.
Наш разработчик Боб не первый раз сталкивается с этой проблемой, ведь он работает в большой компании, где несколько команд ios разработчиков, где нормальная практика для разработки использовать один Apple аккаунт на несколько человек. Да, Боб знает про Xcode плагин https://github.com/neonichu/FixCode, но ведь не заставлять же его насильно всем ставить, разработчики нежные создания, не все любят, когда их заставляют что-то делать против их воли, Боб сам такой.
Бобу всё это настолько надоело, что он уже забыл, что собирался домой 10 минут назад, вместо этого Боб заказывает пиццу, расчехляет макбук, который уже успел упаковать, наливает кофе, просит админов дать доступ до сервера, коннектится туда по ssh и начинает выяснять, в чем же, именно, проблема и как её можно решить.
Ну окей. Первым делом Боб проверяет какие сертификаты, вообще, есть на машине:
security find-identity -v login.keychain
Что выдает
1) 40948A3CA3527F580B9ECB2131DE6B1938FB3D7C "iPhone Developer: Mike ... (KSDA3C3QF2)"
2) 0279CB81AEAD8CE015282DD1FA76CE520A815C4D "iPhone Developer: Bob .. (4WT74HLM2M)"
3) 79A2544B1A63C3F9D3DA3FFAB199FEAADB7EC306 "iPhone Developer: Alica ... (VJ53F2J4EK)"
....
24 valid identities found
Так, как минимум, в keychain, который используется по дэфолту на сервере, есть 24 сертификата. Боб знает, что каждый mobileprovision файл создается на какой-то один конкретный сертификат. Нужно выяснить на какой сертификат создан mobileprovision файл для которого упал билд и понять, что же произошло с сертификатом. Нужно, вообще, понять как mobileprovision файл связан с сертификатом. Боб знает, что develop версия приложения собралась, поэтому на сервере точно есть этот сертификат и сейчас нужно понять как он соотносится с mobileprovision файлом. Для этого Бобу нужно найти mobileprovision файл и данные о сертификате, чтобы начать искать какие-то соответствия между ними.
Ищем mobileprovision файл по его бандлу (если ваш бандл wildcard, то можно искать по любым другим признакам):
cd ~/Library/MobileDevice/Provisioning Profiles
find . -name "*.mobileprovision" -type f -exec grep -H -n -a {} -e "com\.company\.bob" \;
Отлично, мы нашли наш файл:
./f98a06f3-21c2-4de0-975f-5df74197c731.mobileprovision:30: <string>4HUHB9J47M.com.company.bob</string>
В файле есть префикс 4HUHB9J47M у бандла. Из ранее полученного списка сертификатов ничто с этим значением не совпадает. Поэтому Бобу приходится идти в developer.apple.com и искать на какой же аккаунт создан этот mobileprovision файл. Методом тыка он выясняет, что это сертификат Майка:
2) 40948A3CA3527F580B9ECB2131DE6B1938FB3D7C "iPhone Developer: Mike .. (KSDA3C3QF2)"
Отлично, теперь у нас есть с чем работать. Боб у нас не криптоаналитик или секурити ресерчер, но он хороший гуглер, поэтому он с легкостью нашел способ как получить необходимую информацию из сертификата.
Вытаскиваем данные в .pem файл:
security find-certificate -p -c "iPhone Developer: Mike .. (KSDA3C3QF2)" > cert.pem
В файле получаем следующее:
-----BEGIN CERTIFICATE-----
MIIFnjCCBIagAwIBAgIIN8GwnYhLQ/kwDQYJKoZIhvcNAQELBQAwgZYxCzAJBgNV
BAYTAlVTMRMwEQYDVQQKDApBcHBsZSBJbmMuMSwwKgYDVQQLDCNBcHBsZSBXb3Js
ZHdpZGUgRGV2ZWxvcGVyIFJlbGF0aW9uczFEMEIGA1UEAww7QXBwbGUgV29ybGR3
aWRlIERldmVsb3BlciBSZWxhdGlvbnMgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkw
...
....
.....
lD+ocFo6+mab/Ph6mTJOZkZu+hnqhzbTD9Q9dXKWkeXAwTqaESNfnhnuOdfCX3vu
YAz0Hb46G9fkLa5lHjVydbtms685C+uz9Ss4GNRfji1cz5KyblAQAAsqQBUiCwnb
z34=
-----END CERTIFICATE-----
Как оказалось, с этого файла можно считать информацию о сертификате, попробуем получить наиболее интересную для нас:
openssl x509 -noout -fingerprint -in cert.pem
Выдает нам:
SHA1 Fingerprint=40:94:8A:3C:A3:52:7F:58:0B:9E:CB:21:31:DE:6B:19:38:FB:3D:7C
Если убрать двоеточия, то получим 40948A3CA3527F580B9ECB2131DE6B1938FB3D7C, это как раз sha1 сертификата Майка, который мы можем увидеть в UI в Keychain:
Мы также можем получить срок действия сертификата, если нужно написать валидатор истёкших сертификатов:
openssl x509 -noout -startdate -in cert.pem // Feb 27 07:13:41 2016 GMT
openssl x509 -noout -enddate -in cert.pem // Feb 26 07:13:41 2017 GMT
Это же мы видим и в keychain:
В общем с сертификатом всё ясно. «Как же его привязать к нашему mobileprovision файлу?» — думает любопытный Боб. Давайте сначала посмотрим на mobileprovision файл поближе:
security cms -D -i f98a06f3-21c2-4de0-975f-5df74197c731.mobileprovision
Вывод нам дает интересную информацию для поля data, а именно:
<data>
MIIFnjCCBIagAwIBAgIIN8GwnYhLQ/kwDQYJKoZIhvcNAQELBQAwgZYxCzAJBgNV
BAYTAlVTMRMwEQYDVQQKDApBcHBsZSBJbmMuMSwwKgYDVQQLDCNBcHBsZSBXb3Js
ZHdpZGUgRGV2ZWxvcGVyIFJlbGF0aW9uczFEMEIGA1
...
...
YAz0Hb46G9fkLa5lHjVydbtms685C+uz9Ss4GNRfji1cz5KyblAQAAsqQBUiCwnb
z34=
</data>
Где-то это Боб уже видел, похоже на содержание ранее полученного .pem файла:
-----BEGIN CERTIFICATE-----
MIIFnjCCBIagAwIBAgIIN8GwnYhLQ/kwDQYJKoZIhvcNAQELBQAwgZYxCzAJBgNV
BAYTAlVTMRMwEQYDVQQKDApBcHBsZSBJbmMuMSwwKgYDVQQLDCNBcHBsZSBXb3Js
ZHdpZGUgRGV2ZWxvcGVyIFJlbGF0aW9uczFEMEIGA1UEAww7QXBwbGUgV29ybGR3
aWRlIERldmVsb3BlciBSZWxhdGlvbnMgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkw
...
....
.....
lD+ocFo6+mab/Ph6mTJOZkZu+hnqhzbTD9Q9dXKWkeXAwTqaESNfnhnuOdfCX3vu
YAz0Hb46G9fkLa5lHjVydbtms685C+uz9Ss4GNRfji1cz5KyblAQAAsqQBUiCwnb
z34=
-----END CERTIFICATE-----
Тут Боб понял, вот она зацепка mobileprovision файла на сертификат.
Итого, нам нужно взять наш mobileprovision файл, вытащить из него значение для поля data, затем пробежать по всем валидным сертификатам и сравнить с данными из их .pem представления. Боб тут же накидал небольшой скрипт, который передал коллегам из бэкенда, чтобы они его запускали перед каждой сборкой ios проектов. Скрипт запускается достаточно просто:
ruby cert_checker.rb f98a06f3-21c2-4de0-975f-5df74197c731.mobileprovision
require 'active_support/core_ext/hash'
return if ARGV.empty?
xmlString = `security cms -D -i #{ARGV.first}`
data = Hash.from_xml(xmlString)
# получили значение data из mobileprovision
provision_cert_data = data['plist']['dict']['array'].map { |e| e['data'] }.compact.first
# пробегаем по всем сертификатам и собираем их данные в .pem формате
certs = `security find-identity -v login.keychain | grep -o "\\".*\\""`.split("\n").map { |e| e[1..-2] }
pems = certs.map { |e| `security find-certificate -p -c "#{e}"`.split("\n")[1..-2].join('') }
dict = Hash[pems.zip(certs)]
# сравниваем mobileprovision data с .pem данными сертификатов, если совпадет, значит нашли сертификат для mobileprovision,
# если не нашли, значит на машине не установлен сертификат
if dict.keys.keep_if { |e| e == provision_cert_data }.empty?
puts "Have no certificate for file #{ARGV.first}"
else
puts "Your mobileprovision issued for #{dict[provision_cert_data]}"
end
Теперь разработчики могут быстро получить фидбэк от сервера, если с сертификатами что-то не так. Да и вообще впустую не запускать сборку до решения проблемы.
Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.
Комментарии (25)
justhabrauser
29.05.2016 23:09-22«и хочет, чтобы аппа ушла сегодня в стор на апрув, дабы выйграть лишние пару дней»
Для получения хотя бы РВП _не_ гражданину РФ надо сдать экзамен по русика изык очин трудни.
ФМС не туда смотрит, IMHO.
Adnako
30.05.2016 12:09Боб жмет на show more, где его встречает ошибка
Простите, но зачем на сборочном сервере собирать продакшн-релиз и подписывать его сертификатом Developer'а?
Что мешает сделать сертификат для подписи продакшн-релизов, положить его только на сборочный сервер, изменить одну строчку в .xcconfig и забыть про эту ошибку хотя бы на год?house2008
30.05.2016 12:40Допустим в конторе несколько десятков аккаунтов. Разработчик всегда разрабатывает под своим одним аккаунтом. А уже под которым аккаунтом выложить приложение в стор решает бизнес, который заходит в админку приложения и из селекта выбирает на кого собрать и выложить данное приложение. Develop ipa собирается вместе с production, чтобы быть уверенным, что они собраны из одной кодовой базы.
Adnako
30.05.2016 14:02Т.е. проблема в подписи develop ipa, собираемым на сервере?
house2008
30.05.2016 15:17Не) Проблема в том, что куча сертификатов, кто-то их отзывает случайно (revoke), у некоторых истекает срок действия и забывают обновить. Это всё не так просто отследить, как я уже говорил выше, в разработке используются одни аккаунты, а в app store приложения уходят под другими. В данном примере у Боба develop ipa собралась, а production нет, потому что второй сертификат был кем-то отозван (revoked), а новый забыли поставить на машину, хотя mobileprovision был создан на новый сертификат.
Adnako
30.05.2016 15:54Я и предлагаю решение — сертификат для продавлена всего один, подписывать релизные сборки на сборочном сервере надо им, а не каким-то пашей или машей, так больше порядка, имхо.
Adnako
30.05.2016 12:13причем, сначала xcode компилирует зависимости (cocoapods) и уже только после проверяет валидность подписи, то есть только когда собирает основное приложение. Поэтому ошибка проявляется примерно во второй половине процесса сборки, из-за чего первые 6-7 минут потрачены в пустую, от чего Боб расстроен еще больше.
Боб был бы меньше расстроен, если бы предсобранные зависимости статически линковались (динамические — подкладывались) вместо их полной сборки. Собранные ранее бинарники зависимостей — артефакты сборки проектов зависимостей, собираются только при изменении в репозитории.house2008
30.05.2016 12:29Предлагаете все зависимости собирать в отдельные бинарники и затем их подключать руками к проекту вместе с кучей хедер файлов? Возможно, это кому-то удобно, я даже знаю ребят кто так делает, но мне удобнее когда всё динамически само разруливается. Дело вкуса)
Adnako
30.05.2016 14:09Предлагаете все зависимости собирать в отдельные бинарники и затем их подключать руками к проекту вместе с кучей хедер файлов?
Предлагаю собирать зависимости отдельно от проекта и хранить артефакты сборки для использования во время сборки проекта. Всё это автоматически без всяких рук умеет собирать TeamCity.
вместе с кучей хедер файлов?
Тому же TeamCity не важно сколько этих заголовочных файлов он распакует из артефакта-архива для сборки проекта.
Не желаете заголовочные файлы отдельно хранить — делайте статичный или динамический фреймворк и подключайте его — сложного мало.
Возможно, это кому-то удобно, я даже знаю ребят кто так делает, но мне удобнее когда всё динамически само разруливается.
Дело не только в удобстве, этот подход точно уменьшает время сборки и ваш Боб ждал бы не 6-7 минут — когда же соберутся сначала все зависимости, а меньше минуты, пока они скачаются и распакуются.
Но, конечно же, можно кататься на стульях, размахивая палками, пока компилируется — дело вкуса )house2008
30.05.2016 17:15Предлагаете, например, ReactiveCocoa или AFNetworking собирать руками (или через carthage) в бинарник и его руками подключать в проект? А если я хочу исходники глянуть как там что работает? А такое бывает)
Adnako
31.05.2016 08:50Ещё раз вам повторяю — собирать руками не надо, настройте сборку библиотек на сборочном агенте, помечайте тэгом коммит из которого собрались артефакты, подтягивайте артефакты из этой сборки в сборку самого проекта — сборочный агент умеет это делать автоматически без ваших рук.
Все ваши требования удовлетворены, плюс — счастливый Боб, не ожидающий у корыта по 7 минут сборки зависимостей.
Adnako
30.05.2016 12:16просит админов дать доступ до сервера, коннектится туда по ssh и начинает выяснять, в чем же, именно, проблема и как её можно решить.
Боб, похоже, ответственный и образованный программист, раз ему админы легко дают доступ на сборочный сервер по ssh.
Да и админы не успели убежать пораньше в пятницу-то — тоже ответственные товарищи, хорошо знающие Боба и доверяющие ему.house2008
30.05.2016 12:44Всё верно) Боб работает не первый год в этой организации и у него сложились доверительные отношения с админами. Я так и не понял тут вашего сарказма(
Adnako
30.05.2016 14:11Тут не в сарказме дело, ваш сценарий крайне оптимистичен, особенно, для вечера пятницы )
Посыл был в том, что не каждый программист — Боб, не все конторы разрешают лазить каким-то разработчикам по сборочным серверам и что-то там настраивать.house2008
30.05.2016 15:05С этим Бобу повезло) Машинка, на которой собираются ios проекты, это просто отдельная физическая локальная тачка с xcode, cocoapods, и сертификатами. Куда затем с отдельного CI сервера прокидываются команды для сборки проектов и потом просто обратно выкачиваются результаты этого процесса. На сам же CI таким личностям как Боб доступ не положен)
Adnako
31.05.2016 09:03А вообще, могу порекомендовать уже готовых руби-скриптов для автоматизации сборок.
Раз у вас есть доверительная близость с админами, а сам агент полностью ваш — Fastlane вам в помощь.house2008
31.05.2016 09:13Боб знает про fastline) тула крутая конечно, но он огорчен, что sigh resign работает только для простых ipa, если в ipa есть widget или framework, или даже часы, то он с этим не работает, поэтому Бобу пришлось немного допилить под себя.
kpower
06.06.2016 10:21Боб ценит время своих коллег, а потому не пишет UI в коде. Пусть гаденыши общий storyboard мерджат! Уахаха!
kozyabka
… и никаких релизов в пятницу!
MonkAlex
Именно, кто ж релизит\чекинит вечером пятницы.
apachik
стандартная ios-практика. В пятницу «релизим» (заливаем билд на апрув в аппстор), в понедельник продолжаем тестировать и убеждаемся, что все ок (если не ок, то перезаливаем), в среду берут на ревью, в четверг апрувят и выпускаем юзерам.
Так было раньше. Последний месяц ревью занимает день-два: статистика appreviewtimes
taviscaron
странная практика, в идеале тогда релизить нужно в среду, 2 дня на тест и перезаливку без потери 2 дополнительных дней (выходные)
сами релизим в пятницу :) вот только не пытаемся это логически обосновать — логики нет
кстати, в прошедшую пятницу релиз был на ревью 9 часов (девять, ДЕВЯТЬ часов. неужели мы наконец-то приехали туда, куда давно стремились)
apachik
Правило одно — есть возможность — надо заливать как можно раньше, несмотря на день недели.
Как правило, перезаливка все же редкое явление, поэтому лучше залить в пятницу, чем не залить.
Просто на финальное тестирование уходит много времени (мы очень беспокоимся за качество).