使用 CircleCI、fastlane 和 AWS S3 持续集成和交付企业 iOS 应用程序
使用 CircleCI、fastlane 和 AWS S3 持续集成和交付企业 iOS 应用程序
背景: Budbee 是一家为电子商务购物者提供送货上门的最后一英里物流服务。我们是一家位于瑞典斯德哥尔摩的科技创业公司。
在 Budbee,我们有几个 iOS 应用程序,供多个利益相关方使用,例如司机。由于我们的开发必须快速进行,我们使用苹果企业版来发布这些应用程序(我们不可能等待 1-4 周的部署时间)。
我个人一直认为管理代码签名资产和执行 iOS 应用程序的部署是一件麻烦的事情,所以我们决定尽可能地自动化这个过程,以便我们可以专注于更重要的事情。
我们使用 CircleCI 和 fastlane 来持续构建我们的 iOS 应用程序,并将其分发到一个 S3 桶中。这个 bucket 有一个描述最新版本的 JSON 文件,因此如果有新版本可用,我们可以提示用户进行升级。我们使用 sentry 进行崩溃分析,当我们部署新版本时,我们的 dSYMs 会自动上传。
使用 match 管理代码签名资产
每个开发人员都需要有自己的一套用于开发的私有和公共密钥,无论是谁进行分发,都必须在他们的机器上有分发证书。当设置一个新的开发人员或者迁移到一个新的机器上时,你总是会把事情搞砸。Bleh,我们只是想把我们的应用发布出去——所以我们使用 match 来管理我们的代码签名资产。它保持 git 上的一切同步。
- 创建一个新的私有 github repo 来存储配置文件
- 创建一个新的 Apple ID 用于代码签名,该 ID 将在您的团队之间共享(例如 [email protected])
- 运行匹配以生成新的证书和预置描述文件
$ MATCH_FORCE_ENTERPRISE=”1" match enterprise
默认情况下,Match 不允许企业分发,这是由于它的危险性质(如果你的密钥公开,任何人都可以滥用你的公司名称),因此有了MATCH_FORCE_ENTERPRISE变量。我们确保我们的 github repo 是私有的,只有 iOS 开发团队可以访问它,并且他们的帐户有双重认证。
4.按照 match 中的说明,使用您新创建的 Apple ID 进行鉴定。
设置 CircleCI
在 iOS 项目的根目录下,创建一个 circle.yml 文件,并用下面的模板填充它:
machine:
environment:
MATCH_FORCE_ENTERPRISE: "1"
xcode:
version: 8.2dependencies:
pre:
- gem update fastlane
- brew install getsentry/tools/sentry-cli
cache_directories:
- 'vendor/bundle'
- 'budbee-driver/Pods'
- '~/.cocoapods'compile:
override:
- fastlane buildtest:
override:
- fastlane testdeployment:
staging:
branch: develop
commands:
- fastlane stagingrelease:
tag: /v?[0-9]+(\.[0-9]+)*/
commands:
- fastlane deploy
- fastlane dsym
在我们的 circle.yml 中,我们设置环境标志MATCH_FORCE_ENTERPRISE以允许企业分发,将 Xcode 版本设置为 8.2(这里使用您需要的任何版本),更新到 fastlane 的最新版本并安装sentry-cli(用于上传 dSYMs)
你需要一个在 CircleCI 的 OS X 计划来构建 iOS/OSX 项目。遵循您的 github repo,并确保使用 circle.yml 设置开始构建。
您需要在 CircleCI 项目中填充环境变量,即MATCH_PASSWORD(您解锁代码签名证书的密码,您在设置 match 时选择了这个密码)、S3_ACCESS_KEY、S3_SECRET_ACCESS_KEY(您的 AWS IAM keys 可以访问 S3 桶)和SENTRY_AUTH_TOKEN(由 sentry.io 生成的一个 auth 令牌)
我们用不同的通道来分离我们的快速文件,用于编译、测试和分发。当我们将代码推送到任何一个分支时,项目都会被构建和测试。当我们推进到开发分支时,应用程序的试运行版本被部署到 S3,我们推进新的版本标记(v1.2.3 ),生产版本被部署到 S3,dSYM 被上传到 Sentry。
设置快速通道
快速通道/快速文件:
fastlane_version "2.17.1"default_platform :iosplatform :ios dolane :build do
match
gym
enddesc "Runs all the tests"
lane :test do
scan(
workspace: 'budbee-app/myapp.xcworkspace',
scheme: 'myapp',
devices: ['iPhone 5s']
)
enddesc "Deploy new version to S3 bucket"
lane :deploy do
aws_s3(
access_key: ENV['S3_ACCESS_KEY'],
secret_access_key: ENV['S3_SECRET_ACCESS_KEY'],
bucket: 'budbee-apps',
region: 'eu-west-1',
ipa: 'myapp.ipa',
dsym: 'myapp.app.dSYM.zip',
app_directory: 'com.budbee.myapp',
upload_metadata: true,
html_template_path: 'fastlane/s3_ios_html_template.erb',
version_template_path: 'fastlane/s3_ios_version_template.erb',
version_file_name: 'app_version.json'
)
enddesc "Upload dSYM to Sentry"
lane :dsym do
sentry_upload_dsym(
auth_token: ENV['SENTRY_AUTH_TOKEN'],
org_slug: 'budbee-ab',
project_slug: 'budbee-app',
dsym_path: './myapp.app.dSYM.zip'
)
enddesc "Deploy new staging version to S3 Bucket (staging)"
lane :staging do
aws_s3(
access_key: ENV['S3_ACCESS_KEY'],
secret_access_key: ENV['S3_SECRET_ACCESS_KEY'],
bucket: 'budbee-apps',
region: 'eu-west-1',
ipa: 'myapp.ipa',
dsym: 'myapp.app.dSYM.zip',
app_directory: 'com.budbee.myapp.staging',
upload_metadata: true,
html_template_path: 'fastlane/s3_ios_html_template.erb',
version_template_path: 'fastlane/s3_ios_version_template.erb',
version_file_name: 'app_version.json'
)
end
end
快速通道/应用文件:
app_identifier "com.budbee.myapp" # The bundle identifier of your app
apple_id "[[email protected]](mailto:[email protected])" # Your Apple email addressteam_id "[[DEV_PORTAL_TEAM_ID]]" # Developer Portal Team ID
快车道/健身房:
workspace "budbee-app/myapp.xcworkspace"
scheme "myapp"
export_method "enterprise"output_directory "./"
快速通道/匹配文件:
git_url "[[email protected]](mailto:[email protected]):budbee/ios-certificates.git"type "enterprise"readonly trueapp_identifier ["com.budbee.myapp"]
username "[[email protected]](mailto:[email protected])" # Your Apple Developer Portal username
安装所需的 fastlane 插件
$ fastlane add_plugin aws_s3
$ fastlane add_plugin sentry
fast lane/S3 IOs html _ template . erb:
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<title>Install <%= title %></title>
</head>
<body>
<style type="text/css">
* {
font-family: "Helvetica Neue",Helvetica,Arial,sans-serif;
text-align: center;
background-color: #ffffff;
}
.oneRow {
width: 100%;
overflow: auto;
overflow-y: hidden;
white-space: nowrap;
text-align: center;
}
.download {
margin: 30px;
font-size: 130%;
}
a {
text-decoration: none;
color: blue;
}
a:hover {
text-decoration: underline;
}
#finished { display: none; }
#tutorial {
text-align: center;
max-width: 300px;
}
#logo {
text-align: center;
max-width: 150px;
margin-top: 10px;
}
</style><h1 style="text-align: center;"><%= title %></h1><div class="oneRow">
<span class="download" id="ios">
<a href="itms-services://?action=download-manifest&url=<%= plist_url %>" id="text" class="btn btn-lg btn-default" onclick="document.getElementById('finished').id = '';">
Install <%= title %> <%= bundle_version %>
</a>
</span>
</div><h3 id="desktop">Please open this page on your iPhone!</h3><div id="finished">
<p>App is being installed. You may close Safari.</p>
</div><img src="[https://company.com/logo.png](https://carrier-images.s3.amazonaws.com/buyers/logo/1693de3a-f310-4607-b8fc-0a36f791069a.png)" id="logo" />
</body><script type='text/javascript'>
if (/iPhone|iPad|iPod/i.test(navigator.userAgent))
{
document.getElementById("desktop").remove()
}
else
{
document.getElementById("ios").remove()
}
</script>
</html>
fast lane/S3 IOs version _ template . erb:
{
"latestVersion": "<%= bundle_version %>",
"updateUrl": "itms-services://?action=download-manifest&url=<%= plist_url %>"
}
保持应用程序最新
在启动我们的 iOS 应用程序时,它检查 S3 桶是否有新版本可用,如果有,就提示用户更新。
$ pod install Alamofire
$ pod install Version
UpdateManager.swift:
import Foundation
import Alamofire
import Version[@objc](http://twitter.com/objc) class UpdateManager: NSObject {
func checkForUpdate() {
let baseUrl = "[https://budbee-apps.s3-eu-west-1.amazonaws.com](https://budbee-enterprise-apps.s3-eu-west-1.amazonaws.com)"
let bundleIdentifier = Bundle.main.bundleIdentifier!
let url = "\(baseUrl)/\(bundleIdentifier)/app_version.json"
let currentVersion: Version = Version(Bundle.main.infoDictionary?["CFBundleShortVersionString"] as! String)
Alamofire.request(url).responseJSON { response in
if let result = response.result.value {
let JSON = result as! NSDictionary
if let version = JSON.value(forKey: "latestVersion") {
let latestVersion: Version = Version(version as! String)
let updateUrl = JSON.value(forKey: "updateUrl") as! String
if (currentVersion < latestVersion) {
self.displayUpdateAlert(url: updateUrl)
}
}
}
}
}
func displayUpdateAlert(url: String) {
let alertController: UIAlertController = UIAlertController(title: "New Version", message: "There is a new App version available. You must update to continue", preferredStyle: .alert)
let button: UIAlertAction = UIAlertAction(title: "Update App", style: .default) { (action) in
UIApplication.shared.openURL(URL(string: url)!)
}
alertController.addAction(button)
UIApplication.shared.keyWindow?.rootViewController?.present(alertController, animated: true, completion: nil)
}
}
就是这样!您应该能够在任何 iOS 设备上访问您的 S3 桶的 url,并安装企业应用程序。当您部署新版本时,系统会提示您直接从应用程序中进行更新。
黑客中午是黑客如何开始他们的下午。我们是 AMI 家庭的一员。我们现在接受投稿并乐意讨论广告&赞助机会。
要了解更多信息,请阅读我们的“关于”页面 , 喜欢/在脸书给我们发消息,或者简单地,发推文/DM @HackerNoon。