概要
iOSアプリの各種証明書とプロビジョニングプロファイルは1年で有効期限が切れるので、その度に更新が必要ですが、1年に1回なのでけっこう忘れやすいのではないかと思います。特にプッシュ通知の証明書とかは普段意識しないので忘れやすいんじゃないでしょうか。実際、飛ばなくなってから期限切れに気づくということがあったため、再発防止のために定期的に有効期限をチェックしてSlackに投げる仕組みをfastaneのlaneとして実装してみました。
これで来年は同じ過ちを犯さないと思います。
実装方法
以下に実装方法をまとめます。
1. 証明書とプロビジョニングプロファイルの情報をDeveloper Portalから取得する
-
spaceship自体はactionとして用意されていないが、fastlaneに組み込まれていて、sighなどの内部で使われているらしい
-
Spaceship::Portal.loginでログイン(ID/パスはAppfileとFASTLANE_PASSWORDに設定)したあと、Spaceship.provisioning_profile、Spaceship.certificateを呼び出すことでDeveloper PortalのProvisioningProfileとCertificateの情報を取得することができる。
fastlane_require 'spaceship' Spaceship::Portal.login certs = Spaceship.certificate.all profiles = Spaceship.provisioning_profile.all
2. 対象のProvisioningProfileとCertificateの有効期限をチェックして、残り日数に応じて警告を設定する
-
チェック対象とするProvisioningProfileとCertificateに絞る
- 以下のような形で各タイプに絞った証明書やプロビジョニングプロファイルを取得できる
Spaceship.certificate.Production Spaceship.certificate.ProductionPush Spaceship.certificate.VoipPush Spaceship.provisioning_profile.AppStore Spaceship.provisioning_profile.AdHoc ~
- また、certificateとprovisioning_profileは以下のようなプロパティを持っているのでそれらの値でも絞ることができる
-
Spaceship::CertificateのInstance Attribute
expires, id, name, owner_id, owner_name, owner_type, status, type_display_id -
Spaceship::ProvisioningProfileのInstance Attribute
app, certificates, devices, distribution_method, expires, id, managing_app, name, platform, status, type, uuid, version
-
-
取得したcertificate、provisioning_profileのexpiresから残り日数を計算する
3. fastlaneのslackアクションでSlackにいい感じで投稿する
-
Slackで3段階のステータスごとに証明書とプロビジョニングプロファイルを並べて表示する
-
fastlane/slackアクションのattachment_propertiesパラメータにattachmentを渡してアクションを実行する。Slack APIのドキュメントを見るとattachmentsのarrayを送ることができるようだったが、fastlane/slackアクションでは配列を渡せないようだったので、attachmentごとにslackアクションを実行するようにした。
attachments.each do |attachment| slack( message: "", default_payloads: [], slack_url: ENV["WEBHOOK_URL"], use_webhook_configured_username_and_icon: true, attachment_properties: attachment ) end
-
attachmentのデータは以下のような項目からなるhash形式にする。fieldsにcertificateとprovisioning_profileの各アイテムについての表示を配列で入れる。
{ "color": "#2eb886", "pretext": "Optional text that appears above the attachment block", "title": "Slack API Documentation", "title_link": "https://api.slack.com/", "text": "Optional text that appears within the attachment", "fields": [ { "title": "Priority", "value": "High", "short": false } ], ~ }
4. BitriseのScheduled Buildで定期的に有効期限チェックのlaneを実行する
- Scheduled Buildの設定はBuild一覧からStart/Schedule a Buildを選択すれば設定できる。
コード
desc 'Check expirarion date of certificates and provisioning profiles in Developer Center'
desc 'ex: fastlane ios check_expiration_date'
lane :check_expiration_date do
Spaceship::Portal.login
certs = Spaceship.certificate.Production.all + Spaceship.certificate.ProductionPush.all + Spaceship.certificate.VoipPush.all
profiles = Spaceship.provisioning_profile.all.select { |p| p.status != "Invalid" && p.type == "iOS Distribution" }
attachments = get_attachments(certs, profiles)
puts attachments
attachments.each do |attachment|
slack(
message: "",
default_payloads: [],
slack_url: ENV["WEBHOOK_URL"],
use_webhook_configured_username_and_icon: true,
attachment_properties: attachment
)
end
end
CERTIFICATE = "証明書"
MOBILEPROVISION = "プロビジョニングプロファイル"
def get_attachments(certificates, profiles)
cer_profile_list = { CERTIFICATE => certificates, MOBILEPROVISION => profiles }
attachments = []
initial_attachment = {
"pretext" => "📱 CLINICSアプリ(iOS)の証明書・プロビジョニングプロファイル有効期限切れチェック",
"color" => "#FFFFFF"
}
expired_attachment = {
"title" => "以下の#{CERTIFICATE}または#{MOBILEPROVISION}の有効期限が切れています!!",
"color" => "#EEEEEE",
"fields" => []
}
danger_attachment = {
"title" => "以下の#{CERTIFICATE}または#{MOBILEPROVISION}の有効期限がもうすぐ切れます",
"color" => "danger",
"fields" => []
}
warning_attachment = {
"title" => "以下の#{CERTIFICATE}または#{MOBILEPROVISION}の有効期限が近づいています",
"color" => "warning",
"fields" => []
}
other_attachment = {
"title" => "その他の#{CERTIFICATE}と#{MOBILEPROVISION}",
"color" => "good",
"fields" => []
}
last_attachment = {
"text": "Distributionの更新手順は<https://xxxxx|こちら>、APNS・VoIPの更新手順は<https://xxxxx|こちら>",
"actions": [
{
"type": "button",
"text": "View Apple Developer Portal",
"url": "https://developer.apple.com/account/ios/certificate/?teamId=#{ENV["TEAM_ID"]}"
}
],
"color" => "#FFFFFF"
}
cer_profile_list.each() { |type, items|
items.each { |item|
field = get_field(item, type)
days_left = get_days_left(item.expires.to_time)
if days_left < 0
expired_attachment["fields"].push(field)
elsif days_left < ENV["DANGER_DAY"].to_i
danger_attachment["fields"].push(field)
elsif days_left < ENV["WARNING_DAY"].to_i
warning_attachment["fields"].push(field)
else
other_attachment["fields"].push(field)
end
}
}
attachments.push(initial_attachment)
attachments.push(expired_attachment) unless expired_attachment["fields"].empty?
attachments.push(danger_attachment) unless danger_attachment["fields"].empty?
attachments.push(warning_attachment) unless warning_attachment["fields"].empty?
attachments.push(other_attachment) unless other_attachment["fields"].empty?
attachments.push(last_attachment)
end
def get_days_left(expiration_date)
now = Time.now
today = Time.local(now.year, now.month, now.day)
diff = ((expiration_date - today) / 3600 / 24).floor
end
def get_field(item, type)
if type == CERTIFICATE
title = "#{item.owner_name} - #{item.name}"
extension = ".cer"
elsif type == MOBILEPROVISION
title = item.name
extension = ".mobileprovision"
end
days_left = get_days_left(item.expires.to_time)
expires = item.expires.to_time.strftime("%Y/%m/%d")
return {
"title" => "#{title}(#{extension})",
"value" => "残り #{days_left}日 (有効期限:#{expires})"
}
end
参考にしたもの
Documents
Spaceship
fastlane/slack