面向-IOS-开发者的-AWS-和-DevOps-入门指南-三-

131 阅读56分钟

面向 IOS 开发者的 AWS 和 DevOps 入门指南(三)

原文:Beginning DevOps on AWS for iOS Development

协议:CC BY-NC-SA 4.0

八、使用 Fastlane 自动构建、测试和发布

在前一章中,我们学习了如何将存储在 AWS CodeCommit 中的应用源代码与 Jenkins jobs 集成,以便在代码发生更改时实现自动构建。拥有一个在代码更改时自动调用的构建系统是持续集成和持续交付或部署(CICD)的关键要求,因此在本章中,我们将利用自动化构建,深入探讨如何使用 Jenkins 和 Fastlane 来自动化构建、测试和发布 iOS 应用。

使用 Fastlane 匹配和亚马逊 S3 设置代码签名

iOS 开发中的代码签名是一种在 iOS 应用可以分发或上传到 app store 之前对其进行签名的机制。这是保持完整性所必需的,因为它确保 iOS 应用来自已知的可信来源,并且自上次签名以来未被修改。理想情况下,每个开发人员都有一个签名身份,但是当一个团队由多个开发人员组成,并且他们必须管理多个签名身份时,这会产生问题并增加管理开销。

有了 Fastlane 匹配,开发团队可以共享一个代码签名身份,以帮助简单的代码签名。它创建所有必需的签名资源,如证书、配置文件,并将它们存储在一个集中的位置,这样每个团队成员都可以检索这些证书并用于代码签名。当使用 Fastlane 进行应用开发和发布时,这变得无缝,因为从集中位置检索凭证和配置构建系统的过程是内置在 Fastlane 的。在持续集成和持续交付或部署(CICD)系统中使用 Fastlane 时,这一点特别有用,因为可以从集中位置自动检索凭证,并无缝构建应用。

对于代码签名凭证的集中存储,让我们看看如何将亚马逊 S3 用作 Fastlane 匹配的存储位置。在本章的后面,我们将看到如何在应用构建过程中从 S3 检索这些凭证,以便对构建工件进行签名。

正在初始化 Fastlane 匹配

必须首先在本地开发工作站上的项目文件夹中设置 Fastlane 匹配。但是首先必须在开发工作站上安装 Fastlane。安装 Fastlane 有多种方法,但最简单的方法是使用自制软件,如清单 8-1 所示。

$ brew install fastlane

Listing 8-1Installing Fastlane with Homebrew

在 Fastlane 安装之后,从 iOS 项目目录中初始化 match,如清单 8-2 所示。

对于存储模式,如清单 8-2 所示,match 支持不同的存储模式,但我们将重点使用 S3 作为存储模式,因此选择 S3。

**清单 8-2。**初始化匹配

  • 如果命令中出现“无法定位 Xcode”错误,请打开 Xcode 偏好设置,在位置命令行工具中,选择 Xcode CLI(命令行界面)版本,然后再次运行 Fastlane 命令。

初始化时,它在项目目录中创建一个名为 Matchfile 的文件。该文件将包含成功执行 match 所需的所有必要信息。最初,该文件不会包含所有信息,但接下来我们将配置所需的信息。

我们首先为集中式存储创建一个 S3 存储桶,并配置 AWS 凭证环境变量,允许 Fastlane 访问 S3 存储桶,如清单 8-3 所示。还配置了唯一的匹配密码;此密码用于加密/解密证书。

  • 匹配密码是用户选择的任何密码,用作额外的安全层。

记下存储桶名称、用于 AWS 凭证的环境变量名称以及匹配密码。

  • 如果没有为 AWS CLI 配置默认区域,请在 CLI 命令中使用标志--region指定 AWS 区域。
$ aws s3 mb s3://abdullahi-fastlane-certificates

$ export AWS_SECRET_ACCESS_KEY=xxxxxxxxxxx

$ export AWS_ACCESS_KEY_ID=xxxxxxxxxxxx

$ export MATCH_PASSWORD=xxxxxxxxxxx

Listing 8-3Creating S3 bucket and configuring environment variables

使用创建的 S3 存储桶和在清单 8-3 中配置的 AWS 凭证环境变量,从清单 8-2 创建的匹配文件被定制。清单 8-4 中显示了匹配文件的示例内容。

  • 要使用此示例匹配文件,请用此内容替换您的匹配文件的内容,并将 S3 存储桶名称、应用标识符、Apple 用户名和团队 ID 更改为您的内容。
$ cat Matchfile
s3_bucket("abdullahi-fastlane-certificates")

storage_mode("s3")
s3_access_key(ENV["AWS_ACCESS_KEY_ID"])
s3_secret_access_key(ENV["AWS_SECRET_ACCESS_KEY"])

type("development") # The default type, can be: appstore, adhoc, enterprise or development

app_identifier(["com.abdullahiolaoye.sampleapplication"])
username("xxxxxxxxx@email.com") # Your Apple Developer Portal username
team_id("XXXXXXXXXX") # Your team ID

# For all available options run `fastlane match --help`
# Remove the # in the beginning of the line to enable the other options

# The docs are available on https://docs.fastlane.tools/actions/match

Listing 8-4New configured Matchfile

如清单 8-4 所示,除了 S3 bucket name 和 AWS credentials 环境变量,app identifier、Apple developer username 和 team ID 也在 Matchfile 中配置。

在亚马逊 S3 存储证书和密钥

配置好匹配文件后,现在我们可以生成证书并存储在集中式 S3 存储桶中。清单 8-5 展示了如何为开发和示例输出生成证书的示例。清单 8-5 中的命令生成了很多输出,所以只显示了一个片段。

  • 该命令必须从存储匹配文件的项目目录中执行。

  • 清单 8-5 中的命令需要用户输入,例如 Apple ID 密码和钥匙串密码。

**清单 8-5。**Fastlane 生成发展凭证

当该命令完成时,签名凭证将被存储在上述 S3 桶中,如图 8-1 所示。

图 8-1

每个团队 ID 的代码签名凭证存储在 S3 存储桶中

签名凭证由团队 ID 组织,在每个团队 ID 文件夹中,签名凭证的存储如图 8-2 所示。

图 8-2

特定团队 ID 的签名凭据

在 AWS 机密管理器中存储匹配机密和参数

如前所述,使用 Fastlane 匹配进行代码签名的主要好处是易于跨不同的开发人员和构建系统重用一个签名凭据。现在,签名凭证存储在一个集中的 S3 存储桶中,用于正确访问和使用这些存储的代码签名凭证的凭证也必须存储在一个集中的位置,以便在需要时可以方便地检索它们。

到目前为止,这些签名凭证的其他用户将需要的凭证是访问集中式 S3 桶的 AWS 凭证和解密代码签名凭证的匹配密码,所有这些都在清单 8-3 中进行了配置。应该存储的附加信息是 S3 桶名应用标识符苹果用户名团队 ID

为了存储这些内容,我们将使用 AWS Secrets Manager,这是一个专为存储机密而设计的 AWS 服务。图 8-3 显示了如何在 AWS Secrets Manager 中创建一个秘密来存储这些秘密。

图 8-3

创建 AWS 机密管理器机密来存储 Fastlane 机密

在秘密创建之后,它应该在秘密列表上可见。图 8-4 显示了一次成功的秘密创建。

图 8-4

验证秘密创建

接下来,我存储秘密,它应该是可见的,如图 8-5 所示。现在,作为构建过程的一部分,可以手动或以编程方式检索秘密。

图 8-5

AWS 机密管理器中存储的所有机密和参数

  • 图 8-5 中使用的密钥是示例,可以定制成适合您的密钥名称。在后面的部分中,我将展示这些密钥在构建脚本中用于自动秘密检索的地方。

设置 Jenkins 环境

要使用 Jenkins 和 Fastlane 构建和发布 iOS 应用,需要在 Jenkins 环境中安装一些工具。本节将验证所需的工具。

设置和验证 Xcode 安装

对于任何 iOS 应用版本,都必须安装 Xcode。从 App Store 安装 Xcode 时,它也会安装 Fastlane 使用的命令行界面(CLI)工具。

Xcode 安装后,确保接受许可协议并验证 Xcode 已成功启动,如图 8-6 所示。

图 8-6

验证 Xcode 安装

还要验证 Xcode 命令行工具是否如清单 8-6 所示安装。

$ xcode-select --version
xcode-select --version
xcode-select version 2384.

$ xcodebuild -version
xcodebuild -version
Xcode 13.0
Build version 13A233

$ xcodebuild

Command line invocation:
    /Applications/Xcode.app/Contents/Developer/usr/bin/xcodebuild

User defaults from command line:
    IDEPackageSupportUseBuiltinSCM = YES

xcodebuild: error: The directory /Users/userA does not contain an Xcode project.

Listing 8-6Verifying Xcode CLI tools

验证 Fastlane 安装

验证 Fastlane 是否安装在 Jenkins 环境中,如清单 8-7 所示。

**清单 8-7。**验证 Fastlane 安装

设置 AWS CLI

在 Jenkins 构建期间,AWS CLI 将用于检索存储在 AWS Secret Manager 中的所有 Fastlane 机密,因此需要在 Jenkins 环境中安装和配置 AWS CLI。

AWS CLI 可以按照清单 8-8 中所示进行验证和配置。

$ aws –-version
$ aws configure

Listing 8-8Verifying and configuring AWS CLI

安装 JSON 查询语言(jq)

当从 AWS Secrets Manager 中检索机密时,它们以 JSON 的形式返回,并且需要被解析以检索相关的字符串。对于解析,使用 jq,因此它应该安装在 Jenkins 环境中。可以用自制软件安装,如清单 8-9 所示。

$ brew install jq

Listing 8-9Verifying jq installation

用 Fastlane 自动化测试和构建

在这一节中,我们将重点介绍如何与 Fastlane 和 Jenkins 一起自动测试和构建 iOS 应用。

将通道添加到快速文件

首先要将 Fastlane 添加到 iOS 应用项目中,应该在 iOS 项目的根文件夹中创建一个 fastlane 目录,并在 Fastlane 文件夹中创建一个名为 Fastfile 的文件,如清单 8-10 所示。

$ mkdir fastlane

$ cd fastlane && touch Fastfile

Listing 8-10Creating Fastlane directory and Fastfile from project root folder

Fastfile 是用 Ruby 编写的,包含了 Fastlane 在 iOS 应用上执行操作所需的所有指令。快速文件中的指令写在通道中。通道包含在 iOS 应用上执行特定操作的所有逻辑,当调用一组特定的指令时,通道的名称被传递给 Fastlane 命令。

清单 8-11 中显示了可以添加到清单 8-10 中新创建的 Fastfile 中的测试和构建示例通道。

lane :build do
    match(
      type: "appstore",
      storage_mode: "s3",
      s3_bucket: ENV["S3_BUCKET"],
      s3_access_key: ENV["AWS_ACCESS_KEY_ID"],
      s3_secret_access_key: ENV["AWS_SECRET_ACCESS_KEY"],
      app_identifier: ENV["APP_IDENTIFIER"],
      username: ENV["APPLE_DEVELOPER_USERNAME"],
      team_id: ENV["TEAM_ID"]
    )
    increment_build_number(build_number: ENV["BUILD_ID"])
    gym(project: "SampleApp.xcodeproj")
  end

lane :test do
    scan(project: "SampleApp.xcodeproj",
              devices: ["iPhone Xs"])
  end

Listing 8-11Adding Fastlane lanes for test and build to Fastfile

如图所示,有两条车道,即测试建造。在通道配置中,存储在 AWS Secrets Manager 中的所有参数都将被检索,并在构建时通过环境变量提供给 Fastlane。

配置 Jenkins 项目

在前一章中,Jenkins 项目已经建立并与 AWS CodeCommit 集成,用于自动构建。该项目将被扩展,以将 iOS 构建和测试逻辑添加到其构建配置中。在清单 8-12 中可以看到一个示例构建脚本。

set +x
echo "Set fastlane variables..."
export LC_ALL=en_US.UTF-8
export LANG=en_US.UTF-8

echo "Retrieving Secrets from AWS Secret Manager..."
ACCESS_KEY_ID=`aws secretsmanager get-secret-value --secret-id fastlane-secrets --query SecretString --output text | jq -r .AWS_ACCESS_KEY_ID`
SECRET_ACCESS_KEY=`aws secretsmanager get-secret-value --secret-id fastlane-secrets --query SecretString --output text | jq -r .AWS_SECRET_ACCESS_KEY`
MATCH_PASSWORD=`aws secretsmanager get-secret-value --secret-id fastlane-secrets --query SecretString --output text | jq -r .MATCH_PASSWORD`
S3_BUCKET=`aws secretsmanager get-secret-value --secret-id fastlane-secrets --query SecretString --output text | jq -r .S3_BUCKET`
APP_IDENTIFIER=`aws secretsmanager get-secret-value --secret-id fastlane-secrets --query SecretString --output text | jq -r .APP_IDENTIFIER`
APPLE_DEVELOPER_USERNAME=`aws secretsmanager get-secret-value --secret-id fastlane-secrets --query SecretString --output text | jq -r .APPLE_DEVELOPER_USERNAME`
TEAM_ID=`aws secretsmanager get-secret-value --secret-id fastlane-secrets --query SecretString --output text | jq -r .TEAM_ID`

echo "Setting fastlane match environment variables..."
export AWS_ACCESS_KEY_ID=$ACCESS_KEY_ID && export AWS_SECRET_ACCESS_KEY=$SECRET_ACCESS_KEY && export MATCH_PASSWORD=$MATCH_PASSWORD && export S3_BUCKET=$S3_BUCKET && export APP_IDENTIFIER=$APP_IDENTIFIER && export APPLE_DEVELOPER_USERNAME=$APPLE_DEVELOPER_USERNAME && export TEAM_ID=$TEAM_ID

echo "Starting Fastlane..."
fastlane test
fastlane build

Listing 8-12Example test and build script

如清单 8-12 所示,首先从 AWS Secret Manager 中检索参数并配置为环境变量,然后调用测试和构建通道来测试和构建 iOS 应用。该脚本被添加到 Jenkins 中以覆盖现有的构建脚本,如图 8-7 所示。

图 8-7

Jenkins 构建配置

触发 Jenkins 项目

将 Fastlane 构建和测试通道添加到 iOS 项目中,并相应地配置 Jenkins 构建,然后可以在 AWS CodeCommit 中创建新的提交,以开始新的构建。代码可以被提交和推送,如清单 8-13 所示。

$ git add . && git commit -m "Build and test app"
[master ea9e896] Build and test app
 1 file changed, 1 insertion(+), 33 deletions(-)

$ git push
Enumerating objects: 7, done.
Counting objects: 100% (7/7), done.
Delta compression using up to 6 threads
Compressing objects: 100% (4/4), done.
Writing objects: 100% (4/4), 368 bytes | 368.00 KiB/s, done.
Total 4 (delta 2), reused 0 (delta 0), pack-reused 0
To codecommit::us-east-1://devops-ios-repository
   fb68e27..ea9e896  master -> master

Listing 8-13Committing and pushing changes to AWS CodeCommit

这开始了对 Jenkins 的构建,如图 8-8 所示。

图 8-8

开始 iOS 应用测试和构建的新承诺

在 Jenkins 中可以看到构建日志,但是清单 8-14 中显示了构建触发的日志片段。

**清单 8-14。**Fastlane 测试并构建 Jenkins 日志片段

  • Jenkins 必须能够找到所需的安装工具,如 awsjq 。如果您得到诸如“jq: command not found”或“aws: command not found”之类的构建错误,请适当地配置 Jenkins PATH 变量或指定完整路径,其中包括构建脚本中的安装目录,例如/usr/local/bin/aws。

自动发布到 App Store Connect

App Store Connect 允许您管理您的 iOS 应用。从那里,用户可以使用 TestFlight 分发 iOS 应用进行测试,并将应用发布到应用商店。要在 App Store Connect 上管理 iOS 应用,首先要将构建的应用上传到 App Store Connect 门户,然后在那里进行管理。

Fastlane 还自动将 iOS 应用发布到 App Store Connect。在本节中,我们将扩展在上一节中配置的 Jenkins 项目,该项目测试并构建应用,以添加一个将构建的应用发布到 App Store Connect 的操作。

我们将调查 App Store Connect 的两个用例,发布一个应用到 TestFlight 进行 beta 测试,发布一个应用将提交到 App Store。

设置 App Store 连接 API 密钥

为了让 Fastlane 在没有人工干预的情况下以编程方式与 App Store Connect 进行交互,将生成一个 App Store Connect API 密钥来认证 Fastlane。要生成 API 密匙,你从 App Store Connect 仪表盘开始,访问用户,进入页面,如图 8-9 所示。

图 8-9

App Store 连接仪表板

此页面列出了此 Apple developer 帐户的所有用户及其角色。如图 8-10 所示,切换到选项卡。

图 8-10

从“用户和访问”页面访问“密钥”选项卡

如果这是第一次为 App Store Connect API 生成 API key,必须先请求访问,如图 8-11 所示。

图 8-11

请求访问 App Store 连接 API

一旦访问请求被批准(通常是立即批准),就可以生成一个 API 密钥,如图 8-12 所示。

图 8-12

正在生成 App Store 连接 API 密钥

提供 API 密钥的名称和访问级别。示例如图 8-13 所示。

图 8-13

提供 API 键名并生成它

API 密钥生成后,就可以下载了。下载 API 密钥,如图 8-14 所示。

图 8-14

正在下载 API 密钥

  • 此密钥只能下载一次,因此应该存储在安全的位置。如果丢失了,必须重新生成。

下载 API 密钥后,下载选项不再可用,如图 8-15 所示。

图 8-15

下载 API 密钥选项不可用

API 密钥下载在 p8 扩展文件中。要在后续步骤中使用 API 密钥,它应该是 base64 编码的,如清单 8-15 所示。

$ base64 AuthKey_AxxxxxxxBY.p8 > base64output.txt

Listing 8-15Base64 encode App Store Connect API Key

除了下载和 base64 编码的 API 密钥之外,发行者 ID密钥 ID 也应该被检索并存储在一个安全的位置。

在 AWS Secret Manager 中存储机密

从 App Store Connect 门户检索到的所有机密都将被构建过程使用,因此需要将这些机密存储在一个集中的安全位置,以便在构建时可以检索到它们。

对于这个用例,AWS Secrets Manager 将用于存储 App Store Connect secrets,类似于它用于存储 Fastlane 机密和参数的方式。如图 8-16 所示,创建一个新的秘密。

图 8-16

创建机密以存储 App Store 连接机密

然后如图 8-17 所示存储秘密。base64 编码的 API 密钥存储为带有密钥 API_KEY 的值。从 App Store Connect 门户检索的密钥 ID 和发行者 ID 分别存储为带有密钥 KEY_IDApp _ Store _ Connect _ ISSUER _ ID的值。

图 8-17

存储 App Store 连接机密

试飞放行

TestFlight 允许 iOS 开发者邀请真实用户测试应用,以便在应用商店公开发布之前收集反馈。要使用 TestFlight,需要构建应用并发布到 App Store Connect。在这里,我们将看到如何应用发布到试飞可以自动与 Fastlane。

将试飞通道添加到快速文件

扩展之前创建的配置了测试和构建通道的 Fastfile,可以添加一个测试飞行通道。清单 8-16 显示了试飞的一个示例通道配置。

lane :testflight do
  api_key = app_store_connect_api_key(
    key_id: ENV["KEY_ID"],
    issuer_id: ENV["APP_STORE_CONNECT_ISSUER_ID"],
    key_content: ENV["API_KEY"],
    duration: 1200,
    in_house: false,
    is_key_content_base64: true,
  )

  pilot(
    api_key: api_key,
 )
  End

Listing 8-16Fastlane lane for TestFlight release

如清单 8-16 所示,密钥 ID发行者 IDAPI 密钥将从 AWS Secrets Manager 中检索,并通过环境变量提供,供构建流程在运行时使用 Fastlane。

配置 Jenkins 项目

根据清单 8-12 配置的 Jenkins 构建脚本应该被修改以添加新的试飞车道,如清单 8-17 所示。

set +x
echo "Set fastlane variables..."
export LC_ALL=en_US.UTF-8
export LANG=en_US.UTF-8

echo "Retrieving Secrets from AWS Secret Manager..."
ACCESS_KEY_ID=`aws secretsmanager get-secret-value --secret-id fastlane-secrets --query SecretString --output text | jq -r .AWS_ACCESS_KEY_ID`
SECRET_ACCESS_KEY=`aws secretsmanager get-secret-value --secret-id fastlane-secrets --query SecretString --output text | jq -r .AWS_SECRET_ACCESS_KEY`
MATCH_PASSWORD=`aws secretsmanager get-secret-value --secret-id fastlane-secrets --query SecretString --output text | jq -r .MATCH_PASSWORD`
S3_BUCKET=`aws secretsmanager get-secret-value --secret-id fastlane-secrets --query SecretString --output text | jq -r .S3_BUCKET`
APP_IDENTIFIER=`aws secretsmanager get-secret-value --secret-id fastlane-secrets --query SecretString --output text | jq -r .APP_IDENTIFIER`
APPLE_DEVELOPER_USERNAME=`aws secretsmanager get-secret-value --secret-id fastlane-secrets --query SecretString --output text | jq -r .APPLE_DEVELOPER_USERNAME`
TEAM_ID=`aws secretsmanager get-secret-value --secret-id fastlane-secrets --query SecretString  --output text | jq -r .TEAM_ID`
APP_STORE_CONNECT_ISSUER_ID=`aws secretsmanager get-secret-value --secret-id app-store-connect-secret --query SecretString --output text | jq -r .APP_STORE_CONNECT_ISSUER_ID`

KEY_ID=`aws secretsmanager get-secret-value --secret-id app-store-connect-secret --query SecretString --output text | jq -r .KEY_ID`
API_KEY=`aws secretsmanager get-secret-value --secret-id app-store-connect-secret --query SecretString --output text | jq -r .API_KEY`

echo "Setting fastlane match environment variables..."
export AWS_ACCESS_KEY_ID=$ACCESS_KEY_ID && export AWS_SECRET_ACCESS_KEY=$SECRET_ACCESS_KEY && export MATCH_PASSWORD=$MATCH_PASSWORD && export S3_BUCKET=$S3_BUCKET && export APP_IDENTIFIER=$APP_IDENTIFIER && export APPLE_DEVELOPER_USERNAME=$APPLE_DEVELOPER_USERNAME && export TEAM_ID=$TEAM_ID

echo "Setting app store connect secret environemnt variables"
export APP_STORE_CONNECT_ISSUER_ID=$APP_STORE_CONNECT_ISSUER_ID && export KEY_ID=$KEY_ID && export API_KEY=$API_KEY

echo "Starting Fastlane..."
fastlane test
fastlane build
fastlane testflight

Listing 8-17Adding TestFlight to Jenkins build script

存储在 AWS Secrets Manager 中的 App Store Connect secrets 将被检索,并在调用 testflight lane 之前设置为环境变量。

触发 Jenkins 项目

TestFlight lane 已经添加到应用源代码 Fastfile 中,Jenkins 项目也相应地进行了修改,现在可以通过在 AWS CodeCommit 中创建新的 commit 来触发 Jenkins 项目,如清单 8-18 所示。

$ git add . && git commit -m "Upload to testflight"
[master 3a1febb] Upload to testflight
 1 file changed, 17 insertions(+), 1 deletion(-)

$ git push
Enumerating objects: 7, done.
Counting objects: 100% (7/7), done.
Delta compression using up to 6 threads
Compressing objects: 100% (4/4), done.
Writing objects: 100% (4/4), 580 bytes | 580.00 KiB/s, done.
Total 4 (delta 2), reused 0 (delta 0), pack-reused 0
To codecommit::us-east-1://devops-ios-repository
   ea9e896..3a1febb  master -> master

Listing 8-18Creating a new commit in CodeCommit for TestFlight

新提交在 Jenkins 上可见,如图 8-18 所示。

图 8-18

触发试飞发布的新提交

清单 8-19 中显示了触发构建的日志片段。

**清单 8-19。**试飞上传的 Jenkins 日志片段

从 App Store Connect 上的 TestFlight 控制台,可以看到刚刚上传的构建,如图 8-19 所示。

图 8-19

Fastlane 试飞上传

自动发布到应用商店

如果要将应用上传到 App Store Connect 进行 App Store 提交,此过程也可以由 Fastlane 自动完成。

将 App Store Lane 添加到 Fastfile

App Store 的 lane 配置也应该添加到前面操作中使用的同一个 Fastfile 中。清单 8-20 显示了应用商店上传的通道配置示例。

lane :appstore do
  api_key = app_store_connect_api_key(
    key_id: ENV["KEY_ID"],
    issuer_id: ENV["APP_STORE_CONNECT_ISSUER_ID"],
    key_content: ENV["API_KEY"],
    duration: 1200,
    in_house: false,
    is_key_content_base64: true,
  )

  deliver(
    api_key: api_key,
    force: true,
    run_precheck_before_submit: false
  )
  End

Listing 8-20App Store lane configuration

类似地,对于其他通道配置,从构建时环境变量中检索凭证。

配置 Jenkins 项目

App Store 上传的 Jenkins 构建配置与 TestFlight 构建非常相似,因为两者都需要上传到 App Store Connect。唯一的区别是appstore通道是由 Fastlane 调用的,而不是由testflight通道调用的,如清单 8-17 所示。清单 8-21 显示了清单 8-17 中定义的构建脚本的 Fastlane 部分,并显示了应该对构建脚本进行的微小更改。

echo "Starting Fastlane..."
fastlane test
fastlane build
fastlane appstore

Listing 8-21Minor changes required for App Store lane

触发 Jenkins 项目

将 appstore lane 添加到 Fastfile 并相应地配置 Jenkins 构建脚本后,可以在 CodeCommit 中创建新的提交,如清单 8-22 所示。

$ git add . && git commit -m "Upload to appstore"
[master 83fa283] Upload to appstore
 1 file changed, 13 insertions(+), 13 deletions(-)

$ git push
Enumerating objects: 7, done.
Counting objects: 100% (7/7), done.
Delta compression using up to 6 threads
Compressing objects: 100% (4/4), done.
Writing objects: 100% (4/4), 469 bytes | 469.00 KiB/s, done.
Total 4 (delta 2), reused 0 (delta 0), pack-reused 0
To codecommit::us-east-1://devops-ios-repository
   0f80b39..83fa283  master -> master

Listing 8-22Creating new commit for App Store upload

这开始了对 Jenkins 的构建;清单 8-23 显示了 App Store 上传的构建日志片段。

**清单 8-23。**应用商店上传的 Jenkins 日志片段

上传的 build 在 App Store Connect 上可见,如图 8-20 所示。

图 8-20

为 App Store 版本上传的版本

摘要

Fastlane 是一个强大的工具,有助于自动完成 iOS 应用开发中的许多手动任务,将该工具与 Jenkins 集成在一起开辟了更多的自动化可能性。在本章中,我们探讨了其中的一些可能性,并研究了如何使用 Jenkins 自动构建来自动测试、构建和发布 iOS 应用到 App Store Connect。

本章介绍的 Fastlane 测试使用 iOS 模拟器进行测试。接下来,我们将深入使用 AWS Device Farm 在真实设备上测试 AWS 云上的 iOS 应用。

九、将 AWS 设备群用于测试

当在 iOS 模拟器上测试 iOS 应用时,您可以测试应用的功能和行为,但模拟器并不完全像真实的硬件设备一样工作,因此应用在模拟器上可能工作正常,但安装在真实的 iOS 设备上时表现不同。开发期间在真实的移动设备上测试 iOS 应用可以提高应用的质量,因为您可以模拟真实用户将如何与应用交互。

在这一章中,我们将探讨如何在 AWS Device Farm 上测试真实移动设备上的应用,并将 AWS Device Farm 与 Jenkins 集成,以自动化开发和测试流程。

AWS 设备群简介

AWS Device Farm 是一项服务,用于在 AWS 云上托管的真实物理设备(如手机和平板电脑)上测试 iOS、Android 和 web 应用并与之交互。通过 Device Farm,您可以使用各种支持的测试框架为您的应用设置自动化测试,并设置对设备的远程访问,您可以在其中安装应用并与应用实时交互。然而,我们将关注 AWS 设备群的自动化测试特性。

AWS Device Farm 有一些术语,我们在研究该服务时会用到。以下是其中一些术语及其含义:

  • 测试–一个单独的测试用例。

  • 套件(Suite)——组织在测试包中的一组测试,包含一个或多个测试。

  • 工作–用一个或多个套件对单个设备测试单个应用。

  • 运行–在一组设备上运行一组测试的应用的特定版本。一次运行由一个或多个作业组成。

  • 项目–包含运行的逻辑工作空间。每次运行都是针对一个或多个设备对一个应用进行测试。

  • 设备池–具有相似特征的设备集合

要在 AWS Device Farm 上测试 iOS 应用,您需要提供从应用构建中生成的 ipa 应用包,还需要提供您的测试用例或使用内置测试框架,选择用于测试的物理设备,并配置将在其上测试应用的设备状态。测试后,AWS 设备群生成报告;该报告的一些组成部分包括

  • 通过和失败信息

  • 事故报告

  • 测试和设备日志

  • 截屏

  • 工作特性

这些报告将在 AWS 设备群中保留 400 天。

为应用测试生成 ipa 包

要在 AWS 设备场上安排测试运行,需要一个可测试的 iOS 应用归档文件(ipa ),用于测试的 ipa 必须是专门为测试而构建的。

可以从 Xcode 生成测试应用包。可以构建一个应用进行测试,如图 9-1 所示。

图 9-1

构建用于测试的 iOS 应用

构建完成后,会生成一个应用文件,可以导出并用于测试。图 9-2 显示了如何访问存储生成的应用档案的目录位置。

图 9-2

打开生成项目位置

应用档案(带有。app 扩展)应如图 9-3 所示进行检索。

图 9-3

正在检索应用档案以进行测试

可以从图 9-3 中检索的应用档案中创建一个 ipa 档案用于设备群测试,如清单 9-1 所示。

  • 清单 9-1 中的命令必须从图 9-3 所示的 Debug-iphoneos 目录中执行。

    $ mkdir Payload && cp -r SampleApp.app Payload/
    
    $ zip -r Payload.zip Payload && mv Payload.zip SampleApp.ipa
    
    Listing 9-1Generating ipa for Device Farm testing
    
    

如清单 9-1 所示,创建一个负载目录,应用文件被复制到该目录中。然后,该目录被归档并使用 ipa 扩展名重命名。

设置设备场项目并安排测试运行

使用为设备群测试生成的应用 ipa 文件,我们将探索如何通过 AWS 管理控制台和 AWS CLI 来安排 AWS 设备群的测试运行。

使用 AWS 控制台安排测试运行

如图 9-4 所示,可以从 AWS 管理控制台访问 AWS 设备群控制台。

图 9-4

访问 AWS 设备场控制台

  • AWS 设备群目前仅在美国西部-2 AWS 地区可用。

AWS 设备场中的测试在项目中运行;可以使用现有项目,也可以创建新项目,如图 9-5 所示。

图 9-5

创建设备场项目

AWS Device Farm 支持多种测试框架和内置测试类型。目前对于 iOS,Device Farm 支持以下测试框架:

  • 阿皮亚姆

  • 葫芦

  • UI 自动化

  • XCTest

  • XCTest UI

对于不需要您编写或维护测试脚本的内置测试类型,它提供了内置模糊

我们将探索如何使用提供的内置模糊测试和 XCTest UI 测试框架在 AWS 设备群中运行测试。

运行内置模糊测试

内置模糊测试随机向设备发送用户界面事件,然后报告结果。从一个设备群项目中,可以创建一个自动化的测试运行,如图 9-6 所示。

图 9-6

创建自动化测试运行

可以上传应用 ipa 档案,如图 9-7 所示。

图 9-7

上传应用 ipa

应用上传后,会显示应用的一些详细信息,也可以配置运行的名称,如图 9-8 所示。

图 9-8

配置运行名称

  • 默认情况下,运行名称设置为上传的 ipa 应用的名称。这个名字可以改。

接下来,我们通过选择要使用的测试框架的类型来配置测试。图 9-9 显示了从可用选项中选择内置模糊框架。所有默认的模糊配置都可以保持原样。

图 9-9

选择内置模糊测试框架

在提供应用归档和配置测试框架之后,您可以选择测试应用的物理设备的类型。如图 9-10 所示,您可以使用已配置的设备池或创建自己的定制设备池。图 9-10 中使用的设备池是顶级设备;还显示提供的 app 只兼容这两款设备。

图 9-10

选择设备池

您还可以配置设备状态。设备状态配置允许您选择设备语言,并配置网络选项,如 Wi-Fi、蓝牙、GPS 等。,适用于依赖这些设备功能的应用。这些是这个示例应用的默认设置,如图 9-11 所示。

图 9-11

查看设备状态配置并开始运行

一旦开始运行,它就进入挂起状态。运行开始需要几分钟时间,具体取决于物理设备的可用性。

要查看更多详细信息并监控设备池中每个设备上运行的测试,您可以选择该测试,运行详细信息将显示出来,如图 9-12 所示。

图 9-12

挂起状态下运行的详细信息

运行完成后,可以查看运行的状态。显示执行的测试数量及其不同的状态。饼图还显示了百分比通过和其他状态(如果适用),如图 9-13 所示。

图 9-13

运行的测试状态

要查看设备池中特定设备的测试报告详细信息,请选择该设备。从图 9-13 可以查看 iPhone 11 和 iPhone XR 的报告。在图 9-14 中可以看到 iPhone 11 的示例报告。

图 9-14

iPhone 11 的测试报告

运行 XCTest UI 测试

XCTest UI 框架允许您测试应用的 UI 功能。当您构建项目进行测试时,Xcode 会生成测试包。要在 Device Farm 上使用 XCTest UI 框架,可以使用从清单 9-1 中生成的相同应用包,但是必须检索测试包并将其格式化为 ipa 文件,如下所示。

为 XCTest UI 生成测试包

测试包保存在与构建工件相同的位置,可以如图 9-2 所示进行访问。测试包的命名模式为UITests-runner . App,可以检索图 9-1 中样本的测试包,如图 9-15 所示。

图 9-15

正在检索用于测试的 XCTests UI 包

可以从图 9-15 中检索的测试捆绑包中创建 ipa 档案,以实现设备群兼容性,如清单 9-2 所示。

$ mkdir Payload && cp -r SampleAppUITests-Runner.app Payload/

$ zip -r Payload.zip Payload && mv Payload.zip XCTestUI.ipa

Listing 9-2Generating ipa for Device Farm testing

与前面生成的应用构建 ipa 一样,在清单 9-2 中,创建了一个有效负载目录,并将测试包复制到该目录中。然后,该目录被归档并使用 ipa 扩展名重命名。

上传应用和测试包

要开始另一个测试运行,可以如图 9-6 所示开始创建测试运行,如图 9-7 所示上传 app ipa 包。可以修改默认的测试运行名称,如图 9-16 所示。

图 9-16

自定义测试运行名称

对于测试框架,应该从选项中选择 XCTest UI。一旦选择了测试类型,上传从清单 9-2 创建的测试 ipa 包的选项也会出现,如图 9-17 所示。

图 9-17

选择测试框架并上传测试包

一旦测试包被上传,选择测试的执行环境。应选择一个定制环境,YAML 测试规范可如图 9-18 所示进行配置。

图 9-18

配置执行环境和测试规范

测试规范包含了不同的阶段和将被执行来执行这个测试的命令。默认的 YAML 规范可以原样使用,但也可以修改它以添加自定义命令。

创建自定义设备池

下一步是选择一个设备池来运行测试。在图 9-10 中,一个受管理的设备池被用于运行测试,但是让我们探索一下如何创建一个定制的设备池。通过创建自定义设备池,您可以控制选择要在其上运行测试的确切设备。

可以创建一个设备池,如图 9-19 所示。

图 9-19

创建自定义设备池

创建自定义设备池包括为设备池提供一个名称,并选择应该在池中的移动设备。图 9-20 显示了一个只有一个设备的静态设备池创建示例,即 iOS 14.6 的苹果 iPhone X。使用动态设备池,您可以创建规则,以便将符合规则的设备添加到您的设备池中。

图 9-20

使用苹果 iPhone X 的自定义设备池

运行测试

可以选择创建的自定义设备池进行测试,如图 9-21 所示。

图 9-21

选择自定义设备池

如图 9-21 所示,该设备池中只有一个设备,并且该设备与正在测试的应用兼容。其余的配置可以保留为默认,如图 9-11 所示开始运行。一旦创建了试运行,就可以像图 9-12 一样监控其状态。图 9-22 显示了一个完成的 XCTest UI 测试的例子。

图 9-22

已完成 XCTest UI 测试

使用 AWS CLI 计划测试运行

正如 AWS 控制台所示,首先创建一个设备场项目。创建一个项目和示例输出如清单 9-3 所示。

$ aws devicefarm create-project --name SampleApp2 --region us-west-2
{
    "project": {
        "arn": "arn:aws:devicefarm:us-west-2:123456789101:project:abcdefg2-46a7-4ebf-b3ab-7de683ad43cd",
        "name": "SampleApp2",
        "created": "2090-20-20T16:25:11.049000+00:00"
    }
}

Listing 9-3Creating Device Farm project with AWS CLI

用 AWS CLI 创建的项目也可以在 AWS 控制台上看到,如图 9-23 所示。

图 9-23

在 AWS 控制台上查看使用 CLI 创建的项目

  • 该命令返回的 Amazon 资源名称(ARN)应该被存储,并将在该项目的后续 CLI 命令中使用。

通过 AWS CLI 将应用上传到 AWS 设备群的过程分为两个步骤。首先创建一个上传,这将生成一个 S3 预先指定的 URL,文件将被上传到这个 URL。清单 9-4 显示了如何为清单 9-3 中创建的项目创建一个上传,如图 9-23 所示。

  • 将从命令返回的上传 ARN 存储在 arn 部分,如下所示。它将在针对此上传的后续 CLI 命令中使用。
$ aws devicefarm create-upload --project-arn arn:aws:devicefarm:us-west-2:123456789101:project:abcdefg2-46a7-4ebf-b3ab-7de683ad43cd --name SampleApp.ipa --type IOS_APP --region us-west-2
{
    "upload": {
        "arn": "arn:aws:devicefarm:us-west-2:123456789101:upload:abcdefg2-46a7-4ebf-b3ab-7de683ad43cd/0057dee1-a704-43f6-83dd-a993d0ffd4f8",
        "name": "SampleApp.ipa",
        "created": "2090-20-20T16:33:23.578000+00:00",
        "type": "IOS_APP",
        "status": "INITIALIZED",
        "url": "https://prod-us-west-2-uploads.s3-us-west-2.amazonaws.com/arn%3Aaws%3Adevicefarm%3Aus-west-2%3A123456789101%3Aproject%3Aabcdefg2-46a7-4ebf-b3ab-7de683ad43cd/uploads/arn%3Aaws%3Adevicefarm%3Aus-west-2%3A123456789101%3Aupload%3Aabcdefg2-46a7-4ebf-b3ab-7de683ad43cd/0057dee1-a704-43f6-83dd-a36575959ffd4f8/SampleApp.ipa?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Date=20211017T163323Z&X-Amz-SignedHeaders=host&X-Amz-Expires=86400&X-Amz-Credential=AKIAUJHLTYS5AWNTRO6L%2F20211017%2Fus-west-2%2Fs3%2Faws4_request&X-Amz-Signature=4758748956745646fhfgh68768984292",
        "category": "PRIVATE"
    }
}

Listing 9-4Creating an upload for project and output

然后可以将应用上传到创建上传时返回的 S3 签名的 URL,如清单 9-5 所示。

$ curl -T SampleApp.ipa https://prod-us-west-2-uploads.s3-us-west-2.amazonaws.com/arn%3Aaws%3Adevicefarm%3Aus-west-2%3A123456789101%3Aproject%3Aabcdefg2-46a7-4ebf-b3ab-7de683ad43cd/uploads/arn%3Aaws%3Adevicefarm%3Aus-west-2%3A123456789101%3Aupload%3Aabcdefg2-46a7-4ebf-b3ab-7de683ad43cd/0057dee1-a704-43f6-83dd-a36575959ffd4f8/SampleApp.ipa?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Date=20211017T163323Z&X-Amz-SignedHeaders=host&X-Amz-Expires=86400&X-Amz-Credential=AKIAUJHLTYS5AWNTRO6L%2F20211017%2Fus-west-2%2Fs3%2Faws4_request&X-Amz-Signature=4758748956745646fhfgh68768984292

Listing 9-5Uploading application archive

可以使用 AWS CLI 检索上传的状态,如清单 9-6 所示。在使用该应用安排运行之前,状态必须为成功。

  • 可以检索在--arn参数中传递的上传 ARN,如清单 9-4 所示。
$ aws devicefarm get-upload --arn arn:aws:devicefarm:us-west-2:123456789101:upload:abcdefg2-46a7-4ebf-b3ab-7de683ad43cd/0057dee1-a704-43f6-83dd-a993d0ffd4f8 --region us-west-2
{
    "upload": {
        "arn": "arn:aws:devicefarm:us-west-2:123456789101:upload:abcdefg2-46a7-4ebf-b3ab-7de683ad43cd/0057dee1-a704-43f6-83dd-a993d0ffd4f8",
        "name": "SampleApp.ipa",
        "created": "2090-20-20T16:33:23.578000+00:00",
        "type": "IOS_APP

",
        "status": "SUCCEEDED",
        "url": "https://prod-us-west-2-uploads.s3-us-west-2.amazonaws.com/arn%3Aaws%3Adevicefarm%3Aus-west-2%3A123456789101%3Aproject%3Aabcdefg2-46a7-4ebf-b3ab-7de683ad43cd/uploads/arn%3Aaws%3Adevicefarm%3Aus-west-2%3A123456789101%3Aupload%3Aabcdefg2-46a7-4ebf-b3ab-7de683ad43cd/0057dee1-a704-43f6-83dd-a993d0ffd4f8/SampleApp.ipa?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Date=20211017T163908Z&X-Amz-SignedHeaders=host&X-Amz-Expires=86400&X-Amz-Credential=AKIAUJHLTYS5AWNTRO6L%2F20211017%2Fus-west-2%2Fs3%2Faws4_request&X-Amz-Signature=02d8120348419a7007602011f6786c80f53e930ce63c9f6ade13c43e41453530",
        "metadata": "{\"activity_name\":\"\",\"minimum_arm\":6,\"error_type\":null,\"package_name\":\"com.abdullahiolaoye.sampleapplication\",\"sdk_version\":1400,\"files\":{},\"warning_type\":null,\"supported_os\":\"14.0\",\"executable\":\"SampleApp\",\"platform\":[\"iPhoneOS\"],\"form_factor\":[1,2]}",
        "category": "PRIVATE"
    }

}

Listing 9-6Checking upload status

在安排运行之前,需要的一个资源是设备池。要使用内置的设备池,您可以使用 AWS CLI 查看项目可用的设备池,如清单 9-7 所示。

$ aws devicefarm list-device-pools --arn arn:aws:devicefarm:us-west-2:123456789101:project:abcdefg2-46a7-4ebf-b3ab-7de683ad43cd --region us-west-2
{
    "devicePools": [
        {
            "arn": "arn:aws:devicefarm:us-west-2::devicepool:082d10e5-d7d7-48a5-ba5c-b33d66efa1f5",
            "name": "Top Devices",
            "description": "Top devices",
            "type": "CURATED",
            "rules": [
                {
                    "attribute": "ARN",
                    "operator": "IN",
                    "value": "[\"arn:aws:devicefarm:us-west-2::device:CE68825ABE5A4740B56F10111FD47844\",\"arn:aws:devicefarm:us-west-2::device:692AE103025C4C8B8682D22556957C83\",\"arn:aws:devicefarm:us-west-2::device:2CC987870E204BB18E7E36EF5F3C3F45\",\"arn:aws:devicefarm:us-west-2::device:8766BD1DD11744B0ACEA337A6D9BEBEF\",\"arn:aws:devicefarm:us-west-2::device:1F7E40CA59B6467F9443943E14F93848\",\"arn:aws:devicefarm:us-west-2::device:8B4E8F953E0F4460BBC2A18A504D8FC2\",\"arn:aws:devicefarm:us-west-2::device:23E8A18A1966455A8DC1E2ABF8A07F05\",\"arn:aws:devicefarm:us-west-2::device:19B1FD045303482BAE75EE14AFC29AA5\",\"arn:aws:devicefarm:us-west-2::device:57D25EA0789643648E8B4F922B9559AB\",\"arn:aws:devicefarm:us-west-2::device:2330880C98CE4AD287D354F6A58F8450\"]"
                }

            ]
        },
        {
            "arn": "arn:aws:devicefarm:us-west-2::devicepool:1c59cfd0-ee56-4443-b290-7a808d9fd885",
            "name": "Web Performance",
            "type": "CURATED",
            "rules": [
                {
                    "attribute": "ARN",
                    "operator": "IN",
                    "value": "[\"arn:aws:devicefarm:us-west-2::device:2832D5722BEF4FF2B04498ECC4C1C2F6\",\"arn:aws:devicefarm:us-west-2::device:4F74D943F7594EFF96957E238B3CA131\",\"arn:aws:devicefarm:us-west-2::device:5F20BBED05F74D6288D51236B0FB9895\",\"arn:aws:devicefarm:us-west-2::device:1F7E40CA59B6467F9443943E14F93848\",\"arn:aws:devicefarm:us-west-2::device:A7CCFD183C314CE2B44B0BB284AAEA0A\"]"
                }
            ]
        }
    ]
}

Listing 9-7Showing available device pools and output snippet

该命令输出返回设备池及其 Amazon 资源名称(ARN)的列表。

  • 存储设备池的 ARN 以用于计划运行。要使用内置的“Top Devices”池,请存储该设备池的 ARN,如清单 9-7 中所示的命令输出所示。

现在,安排运行所需的所有资源都已配置完毕。可以使用 AWS CLI 使用内置的 Fuzz 测试类型来安排测试运行,如清单 9-8 所示。

$ aws devicefarm schedule-run --project-arn arn:aws:devicefarm:us-west-2:123456789101:project:abcdefg2-46a7-4ebf-b3ab-7de683ad43cd --app-arn arn:aws:devicefarm:us-west-2:123456789101:upload:abcdefg2-46a7-4ebf-b3ab-7de683ad43cd/0057dee1-a704-43f6-83dd-a993d0ffd4f8 --device-pool-arn arn:aws:devicefarm:us-west-2::devicepool:082d10e5-d7d7-48a5-ba5c-b33d66efa1f5 --name SampleApp2.ipa --test type=BUILTIN_FUZZ --region us-west-2
{
    "run": {
        "arn": "arn:aws:devicefarm:us-west-2:123456789101:run:abcdefg2-46a7-4ebf-b3ab-7de683ad43cd/4f324434-2652-45f3-b065-9961e235cfd4",
        "name": "SampleApp2.ipa",
        "type": "BUILTIN_FUZZ",
        "platform": "IOS_APP",
        "created": "2090-20-20T16:45:02.097000+00:00",
        "status": "SCHEDULING",
        "result": "PENDING",
        "started": "2090-20-20T16:45:02.097000+00:00",
        "counters": {
            "total": 0,
            "passed": 0,
            "failed": 0,
            "warned": 0,
            "errored": 0,
            "stopped": 0,
            "skipped": 0
        }

,
        "totalJobs": 2,
        "completedJobs": 0,
        "billingMethod": "METERED",
        "seed": 2134223132,
        "appUpload": "arn:aws:devicefarm:us-west-2:123456789101:upload:abcdefg2-46a7-4ebf-b3ab-7de683ad43cd/0057dee1-a704-43f6-83dd-a993d0ffd4f8",
        "eventCount": 700,
        "jobTimeoutMinutes": 150,
        "devicePoolArn": "arn:aws:devicefarm:us-west-2:123456789101:devicepool:abcdefg2-46a7-4ebf-b3ab-7de683ad43cd/082d10e5-d7d7-48a5-ba5c-b33d66efa1f5",
        "radios": {
            "wifi": true,
            "bluetooth": false,
            "nfc": true,
            "gps": true
        }
    }
}

Listing 9-8Scheduling a test run with AWS CLI

项目 arn 被传递给--project-arn参数,上传 arn 被传递给--app-arn,设备池 arn 被传递给--device-pool-arn参数。

  • 清单 9-8 中安排的示例运行使用“Top Devices”设备池。

  • 从清单 9-8 中的命令输出中检索运行 ARN。这将用于从 AWS CLI 获取运行状态。

可以在 AWS CLI 上查看计划运行的状态,如清单 9-9 所示。

$ aws devicefarm get-run --arn arn:aws:devicefarm:us-west-2:123456789101:run:abcdefg2-46a7-4ebf-b3ab-7de683ad43cd/4f324434-2652-45f3-b065-9961e235cfd4 --region us-west-2
{
    "run": {
        "arn": "arn:aws:devicefarm:us-west-2:123456789101:run:abcdefg2-46a7-4ebf-b3ab-7de683ad43cd/4f324434-2652-45f3-b065-9961e235cfd4",
        "name": "SampleApp2.ipa",
        "type": "BUILTIN_FUZZ",
        "platform": "IOS_APP",
        "created": "2090-20-20T16:45:02.097000+00:00",
        "status": "RUNNING",
        "result": "PENDING",
        "started": "2090-20-20T16:45:02.097000+00:00",
        "counters": {
            "total": 0,
            "passed": 0,
            "failed": 0,
            "warned": 0,
            "errored": 0,
            "stopped": 0,
            "skipped": 0
        }

,
        "totalJobs": 2,
        "completedJobs": 0,
        "billingMethod": "METERED",
        "deviceMinutes": {
            "total": 0.0,
            "metered": 0.0,
            "unmetered": 0.0
        },
        "parsingResultUrl": "https://prod-us-west-2-results.s3-us-west-2.amazonaws.com/123456789101/abcdefg2-46a7-4ebf-b3ab-7de683ad43cd/4f324434-2652-45f3-b065-9961e235cfd4/parsingResult.txt?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Date=20211017T164740Z&X-Amz-SignedHeaders=host&X-Amz-Expires=259200&X-Amz-Credential=AKIAUJHLTYS5AWNTRO6L%2F20211017%2Fus-west-2%2Fs3%2Faws4_request&X-Amz-Signature=95e44a5eaf28a5ea66246a770b3fa85cc3b68e9b6f935e40720e809113094886",
        "seed": 2134223132,
        "appUpload": "arn:aws:devicefarm:us-west-2:123456789101:upload:abcdefg2-46a7-4ebf-b3ab-7de683ad43cd/0057dee1-a704-43f6-83dd-a993d0ffd4f8",
        "eventCount": 700,
        "jobTimeoutMinutes": 150,
        "devicePoolArn": "arn:aws:devicefarm:us-west-2:123456789101:devicepool:abcdefg2-46a7-4ebf-b3ab-7de683ad43cd/082d10e5-d7d7-48a5-ba5c-b33d66efa1f5",
        "radios": {
            "wifi": true,
            "bluetooth": false,
            "nfc": true,
            "gps": true
        }
    }

}

Listing 9-9Checking status of scheduled run

AWS CLI 上安排的运行也可以在 AWS 控制台上查看,如图 9-24 所示。

图 9-24

在 AWS 控制台上查看的 AWS CLI 上计划运行

测试运行完成后,可以在控制台上查看测试报告,如图 9-25 所示。

图 9-25

AWS CLI 上计划运行的测试报告

  • 该报告与图 9-13 所示相同,因为测试使用了相同的应用。

AWS 设备场 Jenkins 插件

AWS Device Farm 可以通过其插件与 Jenkins jobs 集成。通过这种集成,构建 iOS 应用的 Jenkins jobs 可以在构建后将工件发送到 AWS 设备场,以便在真实设备上进行测试,而无需手动干预。

安装插件

AWS Device Farm 插件可以通过 Jenkins 设置页面的管理插件选项与 Jenkins Web UI 一起安装,如图 9-26 所示。

图 9-26

访问 Jenkins 插件

可以搜索并安装插件,如图 9-27 所示。

图 9-27

正在安装 AWS 设备场 Jenkins 插件

配置插件

插件的配置包括提供 AWS 证书,插件将使用这些证书与您的 AWS 帐户中的 AWS 设备群进行交互。该插件支持 IAM 用户凭证或通过 IAM 角色获取临时凭证。无论使用哪种类型的 IAM 标识,插件都需要 AWS 设备场权限来提交测试运行。清单 9-10 显示了一个允许完整设备群访问的 IAM 策略示例。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "DeviceFarmAll",
            "Effect": "Allow",
            "Action": [ "devicefarm:*" ],
            "Resource": [ "*" ]
        }
    ]
}

Listing 9-10Full AWS Device Farm access

通过将受管策略附加到插件将使用的 IAM 用户或 IAM 角色,也可以使用 AWS 设备场受管 IAM 策略。图 9-28 显示了附加到为设备群创建的 IAM 用户的 AWSDeviceFarmFullAccess 托管策略。

图 9-28

使用 AWS 设备场管理策略配置的 IAM 用户

同样,如果使用 IAM 角色来配置插件,图 9-29 显示了使用 AWSDeviceFarmFullAccess 托管策略的 IAM 角色。

图 9-29

使用 AWS 设备场管理策略配置的 IAM 角色

要配置插件,从 Jenkins 设置页面访问系统配置,如图 9-30 所示。

图 9-30

访问插件配置

在“配置”页面上,滚动到“AWS 设备场”部分。图 9-31 显示了带有 IAM 用户凭证的插件配置示例。

图 9-31

使用 IAM 用户凭据配置插件

要在插件中使用 IAM 角色,插件假设 Jenkins 全局环境配置了 AWS 凭证,并且这些凭证将用于承担该角色。

  • Jenkins 上的全局 AWS 凭证的形式可以是附加到 Jenkins 实例的 EC2 IAM 角色(如果 Jenkins 托管在 EC2 上)、在 Jenkins 实例上配置的 AWS 凭证环境变量等。

插件 IAM 角色必须配置为信任 Jenkins 环境使用的 IAM 实体(用户、角色)。清单 9-11 显示了插件 IAM 角色信任关系配置的一个例子。

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "AWS": "<IAM User or Role ARN for Jenkins Environment>"
      },
      "Action": "sts:AssumeRole"
    }
  ]
}

Listing 9-11Trust relationship of the Plugin IAM role

Jenkins 环境中用于承担插件 IAM 角色的 IAM 凭据也必须拥有承担该角色的权限。清单 9-12 中显示了一个 IAM 策略示例,它显示了如何为 Jenkins 环境 IAM 用户或角色配置权限以承担插件 IAM 角色。

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": "sts:AssumeRole",
      "Resource": "<Role ARN of the Device Farm Plugin Role>"
    }
  ]
}

Listing 9-12Configuring permission to assume Plugin IAM role

在确保所有权限都配置为通过设备场插件使用 IAM 角色之后。图 9-32 显示了如何为插件配置 IAM 角色的示例。

图 9-32

使用 IAM 角色配置设备场插件

使用 Jenkins 自动化 AWS 设备群测试

在这一节中,我们将探讨如何将 AWS Device Farm 与 Jenkins 项目集成,该项目已在前一章中为自动化 iOS 应用构建进行了设置。将 AWS Device Farm 添加到 Jenkins 项目中,每当 Fastlane 开发一个应用时,都会自动开始设备场测试。

配置 Jenkins 项目

打开已被配置为构建 iOS 应用的现有 Jenkins 项目,并用清单 9-13 中所示的脚本替换构建脚本。

set +x

export TEAM_ID=`aws secretsmanager get-secret-value --secret-id fastlane-secrets --query SecretString --output text | jq -r .TEAM_ID`

/usr/bin/xcodebuild build-for-testing -scheme SampleApp -destination generic/platform=iOS DEVELOPMENT_TEAM=$TEAM_ID s-allowProvisioningUpdates -derivedDataPath $WORKSPACE

mkdir Payload && cp -r $WORKSPACE/Build/Products/Debug-iphoneos/SampleApp.app Payload/

zip -r Payload.zip Payload && mv Payload.zip SampleApp.ipa

Listing 9-13Jenkins build script for Device Farm testing

该构建脚本从 AWS Secrets Manager 中检索 Apple team ID,并将其配置为环境变量,构建用于使用 Xcode 命令行工具(xcodebuild)进行测试的应用,将构建工件保存到 Jenkins workspace 目录中,并创建一个可测试的 ipa 文件,以备在 AWS 设备场中进行测试。

  • 使用前,在清单 9-13 提供的脚本中输入您的 Jenkins 工作空间目录。

在 Jenkins 上配置的构建脚本示例如图 9-33 所示。

图 9-33

Jenkins 上的设备场构建脚本

在构建脚本中创建的工件将被传递到 AWS 设备场进行测试,因此要配置 AWS 设备场,创建一个构建后操作,如图 9-34 所示。

图 9-34

Jenkins 后期构建操作

在后期构建操作中,选择在 AWS 设备群上运行测试,如图 9-35 所示。

图 9-35

运行 AWS 设备场测试的生成后操作

可提供如图 9-36 所示的配置。应该从下拉列表中选择用于测试的项目和设备池,应该在应用选项中提供构建后 ipa 在 Jenkins 工作区中的存储路径。

图 9-36

Jenkins 项目上的设备场构建后配置

图 9-36 中的示例显示了一个 ipa,它在构建后将被命名为 SampleApp.ipa,并存储在工作空间的根目录中(如清单 9-13 中所配置的)。

Jenkins 将在 AWS 设备场上创建的测试运行的名称也可以配置,默认情况下,build 标记用于命名创建的测试运行。

还应该选择要运行的测试框架和执行环境。图 9-37 显示了一个使用内置模糊测试框架并在定制环境中执行测试运行的例子。

图 9-37

配置测试框架和执行环境

触发 Jenkins 项目

通过在 AWS CodeCommit 中创建一个 commit,可以在 Jenkins 项目上触发自动构建。您还可以在 Jenkins 上手动启动一个构建来测试新处理的构建后操作。

可以如图 9-38 所示启动手动 Jenkins 构建。

图 9-38

开始手动 Jenkins 构建

清单 9-14 中显示了一个构建后设备群的 Jenkins 构建日志示例。正如所看到的,ipa 文件在测试安排之前首先被上传到 S3。运行也使用 Jenkins build 标记命名。

[AWSDeviceFarm] Using Project 'SampleApp'
[AWSDeviceFarm] Using DevicePool 'Top Devices'
[AWSDeviceFarm] Using App 'SampleApp.ipa'
[AWSDeviceFarm] Archiving artifact 'SampleApp.ipa'
[AWSDeviceFarm] Uploading SampleApp.ipa to S3
[AWSDeviceFarm] Waiting for upload SampleApp.ipa to be ready (current status: INITIALIZED)
[AWSDeviceFarm] Upload SampleApp.ipa succeeded
[AWSDeviceFarm] Getting test to schedule.
[AWSDeviceFarm] Scheduling 'BUILTIN_FUZZ' run 'jenkins-TestProject-96'
[AWSDeviceFarm] View the BUILTIN_FUZZ run in the AWS Device Farm Console: https://console.aws.amazon.com/devicefarm/home?#/projects/aaaabbbcc-33ab-4d34-87c6-576uhfdyur4/runs/abfj598n-ecfb-44cb-9d30-5958fhmfjhd
[AWSDeviceFarm] Waiting for test run to complete.

Listing 9-14Device Farm Jenkins build logs

一旦构建完成,可以在 Jenkins 项目主页上看到结果。该页面显示了处于通过、警告、跳过、失败、错误和停止状态的测试数量。图 9-39 显示,对于最近执行的测试,六项测试全部通过。

图 9-39

Jenkins 项目页面上的测试状态

可以在设备群控制台上查看详细的测试报告。图 9-40 显示了 Jenkins 测试运行的报告。

图 9-40

AWS 设备群控制台上的测试报告

摘要

将 AWS 设备场测试引入 iOS 应用开发,可以让您模拟真实环境来运行测试和重现问题,从而提高质量和用户体验。AWS Device Farm 可用的插件和 APS 也使得将它与开发工作流相集成变得可行。在本章中,我们探讨了 AWS device farm 的主要优势,并介绍了它如何与 Jenkins 上的 iOS 开发工作流集成。

在下一章中,我们将研究本章和前面章节中涉及的所有主要开发组件是如何作为持续集成持续交付(CICD)管道中的阶段被编排在一起的。

十、iOS 应用开发的持续交付渠道

持续集成持续交付(CICD)管道是从源代码和版本控制系统到向用户和客户交付新软件版本的软件开发步骤的自动化定义。在前面的章节中,我向 iOS 应用开发的不同方面和生命周期介绍了 DevOps 概念和工具,从构建、测试到交付。在这一章中,我将展示 CICD 管道如何帮助自动化和编排所有这些不同的组件到一个简化的过程中。

虽然 CICD 管道有多种选择,但我将重点关注 Jenkins 管道和 AWS 代码管道。我将展示这些管道类型如何与 iOS 开发工具集成,以实现 iOS 应用开发的 CICD。

Jenkins 管道公司

根据 Jenkins 网站的说法,“Jenkins Pipeline(或简称为大写“P”的“Pipeline”)是一套插件,支持将持续交付管道实现和集成到 Jenkins 中。”

对于使用 Jenkins Pipeline for CICD 的给定项目,管道被定义为名为“Jenkinsfile”的文本文件中的代码,并且该文本文件预计是项目源代码控制库的一部分,因此管道可以像应用代码的其余部分一样受到版本控制。编写管道 Jenkinsfile 时,支持的不同语法有声明式和脚本式。我将介绍声明性语法,因为它比较新,并且更容易阅读和理解 Jenkins 管道代码。另一方面,脚本语法遵循 Groovy 语言语法。

  • 作为在 Jenkinsfile 中定义语法的替代方法,管道语法也可以在 Jenkins UI 上定义,但最佳做法是将管道语法定义为代码,并将其与应用代码的其余部分一起存储,以便进行版本控制。

建立 Jenkins 管道的第一步是在 Jenkins 上创建管道。如图 10-1 所示,可以开始创建管道。

图 10-1

建立 Jenkins 管道

  • 避免在管道名称中使用空格,因为已知空格会在管道调用过程中导致问题。

就像前面章节中设置一个 Jenkins 项目一样,您可以使用 CodeCommit 存储库为 Jenkins 管道配置构建触发器,如图 10-2 所示。

图 10-2

为管道设置生成触发器

如图 10-3 所示,我正在配置管道以获取管道脚本,即来自源代码提交存储库的 Jenkinsfile,然后配置存储库和凭证。

图 10-3

配置管道定义位置

还必须定义管道脚本在存储库中的确切位置。在本例中,管道脚本位置的配置如图 10-4 所示。

图 10-4

配置管道脚本路径

配置好管道脚本路径后,保存管道创建即可完成创建,如图 10-4 所示。

  • Jenkins 管道脚本文件大多命名为 Jenkinsfile 该名称可以更改为您选择的任何名称。无论使用什么名称,都应如图 10-4 所示进行配置。

如图 10-4 所示,必须将 Jenkinsfile 添加到源存储库中。图 10-5 显示了添加 Jenkinsfile 后的存储库结构。

图 10-5

添加了 Jenkinsfile 的存储库结构

  • 图 10-5 所示的示例应用代码与前面章节中使用的代码相同。

Fastlane 测试阶段

已经在 Jenkins 上设置了 pipeline,并将 Jenkinsfile 添加到源存储库中,您可以通过向 Jenkinsfile 添加内容,开始向表示 iOS 应用开发过程中不同步骤的 Pipeline 添加阶段。首先让我们看看如何添加一个运行 Fastlane 测试的阶段。

清单 10-1 显示了一个 Jenkinsfile,它定义了运行 Fastlane 测试的单级管道。

pipeline {
    agent any
    stages {
        stage("Run fastlane test") {
            steps{
                sh """
                export LC_ALL=en_US.UTF-8 && export LANG=en_US.UTF-8
                fastlane test
                """
            }
        }
    }
}

Listing 10-1Single-stage Pipeline definition to run Fastlane test

当这些更改被提交并推送到 CodeCommit 时,管道触发并读取存储库中的 Jenkinsfile 来定义管道中的步骤。清单 10-1 中定义的流水线的成功执行如图 10-6 所示。

图 10-6

Fastlane 测试的单级流水线执行

  • Jenkins 一定能找到 Fastlane。如果出现找不到 Fastlane 的错误,请适当配置 Jenkins PATH 变量或在 Jenkinsfile 中指定 Fastlane 安装目录的完整路径。

此外,可以检索管道日志来查看执行和步骤的更多细节。图 10-6 中显示的管道执行的日志片段在清单 10-2 中给出。

Started by remote host SQSTrigger with note: User invoked: arn:aws:iam::123456789106:user/abdullahi2
Obtained jenkins/Jenkinsfile from git https://git-codecommit.us-east-1.amazonaws.com/v1/repos/devops-ios-repository
Running in Durability level: MAX_SURVIVABILITY
[Pipeline] Start of Pipeline
[Pipeline] node
.
.
.
.
[Pipeline] // stage
[Pipeline] withEnv
[Pipeline] {
[Pipeline] stage
[Pipeline] { (Run fastlane test)
[Pipeline] sh
+ export LC_ALL=en_US.UTF-8
+ LC_ALL=en_US.UTF-8
+ export LANG=en_US.UTF-8
+ LANG=en_US.UTF-8
+ fastlane test
.
.
.
.
[Pipeline] }
[Pipeline] // stage
[Pipeline] }
[Pipeline] // withEnv
[Pipeline] }
[Pipeline] // node
[Pipeline] End of Pipeline
Finished: SUCCESS

Listing 10-2Jenkins Pipeline execution logs

AWS 设备场测试阶段

要将设备场阶段添加到管道,必须安装 AWS 设备场 Jenkins 插件。在前一章中,我演示了如何使用 Jenkins freestyle 项目安装插件并与之交互。但是在这里,我们将通过管道与插件交互,所以让我们生成准确的语法来做到这一点。您可以从 Jenkins 管道中访问管道语法生成器,如图 10-7 所示。

图 10-7

访问管道语法生成器

如图 10-8 所示,从下拉选项中选择 AWS 设备农场步骤。

图 10-8

选择 AWS 设备场步骤

进入如图 9-36 和 9-37 所示的 Device Farm 测试配置(来自第九章),并为输入的配置生成如图 10-9 所示的流水线脚本。

图 10-9

为 AWS 设备场步骤生成管道脚本

应复制生成的管道脚本,并将其用于配置管道 Jenkinsfile 中的设备场阶段,如清单 10-3 所示,其中显示了将设备场阶段的配置附加到现有示例应用 Jenkinsfile。

pipeline {
    agent any
    environment {
        TEAM_ID = """${sh(
            returnStdout: true,
            script: 'aws secretsmanager get-secret-value ​--secret-id fastlane-secrets --query SecretString ​--output text | jq -r .TEAM_ID'
        ).trim()}"""
    }
    stages {
        stage("Run fastlane test") {
            steps{
                sh """
                export LC_ALL=en_US.UTF-8 && export LANG=en_US.UTF-8
                fastlane test
                """
            }
        }
        stage("Run AWS Device Farm test") {
            steps {
                sh '/usr/bin/xcodebuild build-for-testing ​-scheme SampleApp -destination generic/platform=iOS DEVELOPMENT_TEAM=${TEAM_ID} ​-allowProvisioningUpdates -derivedDataPath ${WORKSPACE}'
                sh 'mkdir Payload && cp -r ${WORKSPACE}/Build/Products/Debug-iphoneos/SampleApp.app Payload/'
                'zip -r Payload.zip Payload && mv Payload.zip SampleApp.ipa'

                devicefarm appArtifact: 'SampleApp.ipa', appiumJavaJUnitTest: '', appiumJavaTestNGTest: '', appiumNodeTest: '', appiumPythonTest: '', appiumRubyTest: '', appiumVersionJunit: '1.4.16', appiumVersionPython: '1.4.16', appiumVersionTestng: '1.4.16', calabashFeatures: '', calabashProfile: '', calabashTags: '', deviceLatitude: 47.6204, deviceLocation: false, deviceLongitude: -122.3941, devicePoolName: 'Top Devices', environmentToRun: 'StandardEnvironment', eventCount: '', eventThrottle: '', extraData: false, extraDataArtifact: '', ifAppPerformanceMonitoring: true, ifBluetooth: true, ifGPS: true, ifNfc: true, ifSkipAppResigning: false, ifVideoRecording: true, ifVpce: false, ifWebApp: false, ifWifi: true, ignoreRunError: false, isRunUnmetered: false, jobTimeoutMinutes: 60, junitArtifact: '', junitFilter: '', password: '', projectName: 'SampleApp', radioDetails: false, runName: '${BUILD_TAG}', seed: '', storeResults: false, testSpecName: 'Default TestSpec for Android Appium Java Junit', testToRun: 'BUILTIN_FUZZ', uiautomationArtifact: '', uiautomatorArtifact: '', uiautomatorFilter: '', username: '', vpceServiceName: '', xctestArtifact: '', xctestFilter: '', xctestUiArtifact: '', xctestUiFilter: ''
            }
        }
    }
}

Listing 10-3Adding Device Farm Stage to Jenkins Pipeline

在现在添加的设备场阶段,定义了多个步骤。首先,应用是为使用 xcodebuild 进行测试而构建的,以生成测试工件。从生成的测试工件中,创建一个 ipa 文件并存储在一个位置,然后由设备群插件获取该文件以进行设备群测试运行。

环境变量也用于 TEAM_ID 的那个阶段。该环境变量在管道定义的环境部分中定义。环境变量的值是在运行时从 AWS Secrets Manager 中动态检索的。

当这些更改被提交并推送到 AWS CodeCommit 存储库时,管道就会触发。清单 10-3 中定义的流水线的成功执行如图 10-10 所示。

图 10-10

AWS 设备场测试的管道执行

测试结果也可以在 AWS 设备群上可视化,如图 10-11 所示。

图 10-11

Jenkins pipeline AWS 设备群测试结果

Fastlane 构建阶段

当所有的应用测试都通过后,通常下一步就是构建应用。这里,我们将添加一个阶段,在 Fastlane 和设备场测试成功后,构建应用以供 App Store 分发。

清单 10-4 显示了将 Fastlane 构建阶段附加到现有管道 Jenkinsfile 的代码片段。

pipeline {
    agent any
    environment {
        TEAM_ID = """${sh(
            returnStdout: true,
            script: 'aws secretsmanager get-secret-value ​--secret-id fastlane-secrets --query SecretString ​--output text | jq -r .TEAM_ID'
        ).trim()}"""
        AWS_ACCESS_KEY_ID = """${sh(
            returnStdout: true,
            script: 'aws secretsmanager get-secret-value ​--secret-id fastlane-secrets --query SecretString ​--output text | jq -r .AWS_ACCESS_KEY_ID'
        ).trim()}"""
        AWS_SECRET_ACCESS_KEY = """${sh(
            returnStdout: true,
            script: 'aws secretsmanager get-secret-value ​--secret-id fastlane-secrets --query SecretString ​--output text | jq -r .AWS_SECRET_ACCESS_KEY'
        ).trim()}"""
        MATCH_PASSWORD = """${sh(
            returnStdout: true,
            script: 'aws secretsmanager get-secret-value ​--secret-id fastlane-secrets --query SecretString ​--output text | jq -r .MATCH_PASSWORD'
        ).trim()}"""
        S3_BUCKET = """${sh(
            returnStdout: true,
            script: 'aws secretsmanager get-secret-value ​--secret-id fastlane-secrets --query SecretString ​--output text | jq -r .S3_BUCKET'
        ).trim()}"""
        APP_IDENTIFIER = """${sh(
            returnStdout: true

,
            script: 'aws secretsmanager get-secret-value ​--secret-id fastlane-secrets --query SecretString ​--output text | jq -r .APP_IDENTIFIER'
        ).trim()}"""
        APPLE_DEVELOPER_USERNAME = """${sh(
            returnStdout: true,
            script: 'aws secretsmanager get-secret-value ​--secret-id fastlane-secrets --query SecretString ​--output text | jq -r .APPLE_DEVELOPER_USERNAME'
        ).trim()}"""
    }

    stages {
        stage("Run fastlane test") {
            steps{
                       .
                       .
            }
        }
        stage("Run AWS Device Farm test") {
            steps {
                        .
                          .
            }
        }
        stage("Build app") {
            steps{
                sh """
                export LC_ALL=en_US.UTF-8 && export LANG=en_US.UTF-8
                fastlane build
                """
            }
        }
    }

}

Listing 10-4Adding Fastlane build stage to pipeline

在添加的 Fastlane 构建阶段,步骤包括设置一些特定于 Fastlane 的环境变量,并调用 Fastfile 中定义的构建通道。还定义了 Fastlane 所需的其他环境变量。所有这些环境变量值都是在运行时从 AWS Secrets Manager 中动态检索的。

将更改推送到 AWS CodeCommit 存储库后,清单 10-4 中定义的管道成功执行,如图 10-12 所示。

图 10-12

应用测试和构建的流水线执行

Fastlane 交付阶段

一个成功的 iOS 构建意味着应用已被签名,并准备好发布以进行试飞测试或提交到 App Store。在这里,我们将向管道添加一个阶段,调用应用中配置的 Fastlane Testflight lane,将应用交付给 App Store Connect,以便使用 Testflight 进行 beta 测试。

清单 10-5 显示了将交付阶段附加到现有管道 Jenkinsfile 以将构建的工件交付到 TestFlight 的代码片段。

pipeline {
    agent any
    environment {
        TEAM_ID = """${sh(
            returnStdout: true,
            script: 'aws secretsmanager get-secret-value ​--secret-id fastlane-secrets --query SecretString ​--output text | jq -r .TEAM_ID'
        ).trim()}"""
        AWS_ACCESS_KEY_ID = """${sh(
            returnStdout: true,
            script: 'aws secretsmanager get-secret-value ​--secret-id fastlane-secrets --query SecretString ​--output text | jq -r .AWS_ACCESS_KEY_ID'
        ).trim()}"""
        AWS_SECRET_ACCESS_KEY = """${sh(
            returnStdout: true,
            script: 'aws secretsmanager get-secret-value ​--secret-id fastlane-secrets --query SecretString ​--output text | jq -r .AWS_SECRET_ACCESS_KEY'
        ).trim()}"""
        MATCH_PASSWORD = """${sh(
            returnStdout: true,
            script: 'aws secretsmanager get-secret-value ​--secret-id fastlane-secrets --query SecretString ​--output text | jq -r .MATCH_PASSWORD'
        ).trim()}"""
        S3_BUCKET = """${sh(
            returnStdout: true,
            script: 'aws secretsmanager get-secret-value ​--secret-id fastlane-secrets --query SecretString ​--output text | jq -r .S3_BUCKET'
        ).trim()}"""
        APP_IDENTIFIER = """${sh(
            returnStdout: true

,
            script: 'aws secretsmanager get-secret-value ​--secret-id fastlane-secrets --query SecretString ​--output text | jq -r .APP_IDENTIFIER'
        ).trim()}"""
        APPLE_DEVELOPER_USERNAME = """${sh(
            returnStdout: true,
            script: 'aws secretsmanager get-secret-value ​--secret-id fastlane-secrets --query SecretString ​--output text | jq -r .APPLE_DEVELOPER_USERNAME'
        ).trim()}"""
        APP_STORE_CONNECT_ISSUER_ID = """${sh(
            returnStdout: true,
            script: 'aws secretsmanager get-secret-value ​--secret-id app-store-connect-secret --query SecretString --output text | jq -r .APP_STORE_CONNECT_ISSUER_ID'
        ).trim()}"""
        KEY_ID = """${sh(
            returnStdout: true,
            script: 'aws secretsmanager get-secret-value ​--secret-id app-store-connect-secret --query SecretString --output text | jq -r .KEY_ID'
        ).trim()}"""
        API_KEY = """${sh(
            returnStdout: true,
            script: 'aws secretsmanager get-secret-value ​--secret-id app-store-connect-secret --query SecretString --output text | jq -r .API_KEY'
        ).trim()}"""
    }
    stages {
        stage("Run fastlane test") {
            steps{
                              .
                              .
            }

        }
        stage("Run AWS Device Farm test") {
            steps {
                              .
                              .
            }
        }
        stage("Build app") {
            steps{
                              .
                              .
            }
        }
        stage("Deliver app") {
            steps{
                sh """
                export LC_ALL=en_US.UTF-8 && export LANG=en_US.UTF-8
                fastlane testflight
                """
            }
        }
    }
}

Listing 10-5Adding Fastlane deliver stage to pipeline

在这里添加的交付阶段,步骤包括设置一些 Fastlane 特定的环境变量,并调用 Fastfile 中定义的 Testflight lane。还定义了 Fastlane 所需的附加环境变量,以便将构建工件上传到 App Store Connect。

清单 10-5 中定义的流水线成功执行如图 10-13 所示。

图 10-13

应用测试、构建和交付的管道执行

AWS 代码管道

AWS CodePipeline 是 AWS 的托管 CICD 服务。使用 CodePipeline,您可以自动化软件发布过程的不同阶段,而无需担心管理管道基础设施。它还在不同阶段与不同的 AWS 服务和第三方工具集成,以增强软件开发,例如,它与 Device Farm 集成以进行应用测试,与 GitHub 集成以进行源代码控制,与 Jenkins 集成以进行定制构建和测试阶段。

在接下来的部分中,我将介绍如何为 AWS 代码管道集成准备一个 Jenkins 环境,然后通过演示如何在 AWS 代码管道上使用 CICD 管道运行 Fastlane 测试和 AWS 设备场测试来深入研究集成。使用将要展示的概念,您将能够扩展您的 CICD 管道,以覆盖更多场景。

为 AWS 代码管道设置 Jenkins 环境

要在 AWS 代码管道上配置调用 Jenkins 进行自定义操作的管道,必须有一个外部 Jenkins 服务器,并且必须配置 Jenkins 服务器以启用与 AWS 代码管道服务的交互。

让我们探讨一些完成最终目标必须具备的先决条件。

设置 macOS 构建服务器

在本节中,我将使用 Jenkins 控制器和代理架构。请参考第四章,其中我介绍了如何提供 EC2 macOS 服务器并连接到它,并参考第五章,其中您看到了如何在 Linux 和 iOS 上设置 Jenkins 控制器,并使用 EC2 macOS 构建代理架构。

当设置成功时,在线 macOS 构建服务器将如图 10-14 所示。

图 10-14

EC2 macOS 在线构建服务器

验证 Fastlane 是否安装在 macOS 构建代理上,如清单 10-6 所示。

在 Jenkins 构建期间,AWS CLI 将用于检索存储在 AWS Secrets Manager 中的所有 Fastlane 机密,因此需要在 macOS 服务器上安装和配置 AWS CLI。

AWS CLI 可以按照清单 10-7 中所示进行验证和配置。

$ aws –-version
$ aws configure

Listing 10-7Verifying and configuring AWS CLI

jq 还用于在构建期间解析 jSON 数据,因此应该安装在 macOS 服务器上。可以用自制软件安装,如清单 10-8 所示。

$ brew install jq

Listing 10-8Verifying jq installation

设置 Xcode 和钥匙串访问

Xcode 必须使用将用于 Jenkins 作业的系统用户来安装和配置。

  • 通常,在构建代理上为 Jenkins 创建一个新用户,例如,在第五章设置 Jenkins 控制器和构建代理中,Jenkins 控制器将使用一个名为**“Jenkins”**的 Jenkins 用户通过 SSH 连接到 macOS 构建代理以提交构建作业。

  • 必须使用 Jenkins 用户,而不是默认的 ec2-user ,在 EC2 Mac 实例上安装和配置 Xcode。请参阅第四章,了解如何为 Jenkins 用户创建密码,并通过 VNC 上的 Jenkins 用户连接到您的 EC2 Mac 实例 UI。

此外,确保在构建服务器上的 Xcode 中正确配置了应用签名。图 10-15 显示了在 EC2 Mac 服务器上为将用于演示的示例应用配置 Xcode 签名和功能的示例。

图 10-15

Xcode 上的签名和功能设置

当使用 Mac server 作为构建代理在 Jenkins 上运行 CICD 进程时,构建通常会失败,因为在签名构建工件需要访问证书时,用户交互不可用于提供钥匙串密码。

为了防止这种情况,我将执行的步骤之一是在构建服务器钥匙串上配置签名证书,以便当 Xcode 在管道执行期间尝试使用它时,它允许访问而不提示输入密码。

图 10-16 所示的场景包括允许访问钥匙串中的证书。但是在使用“钥匙串”中的项目之前,钥匙串本身必须解锁。虽然这可以在管道执行期间自动完成,但需要钥匙串密码来解锁。在图 10-17 中,我将我的钥匙串密码存储在 AWS Secrets Manager 中,这样就可以在管道运行时检索到它。

图 10-17

将钥匙串密码存储在 AWS Secrets Manager 中

图 10-16

在钥匙串上配置签名证书权限

设置 EC2 IAM 角色

当将 Jenkins 与 AWS 代码管道集成时,Jenkins 服务器必须具有与 AWS 代码管道服务交互的权限,以轮询作业、报告作业状态等。对于所需的权限,有一个 AWS 管理的策略awscodepielinecustomactionaccess包含所有所需的权限。机密也存储在 AWS Secrets Manager 中,并在管道执行期间检索,因此必须向 Jenkins 服务器提供获取这些机密的权限。

当对 Jenkins 服务器使用 EC2 时,可以跨所有 Jenkins 节点(控制器和构建代理)使用单个 Jenkins EC2 IAM 角色来满足权限要求。图 10-18 显示了 Jenkins EC2 IAM 角色的权限配置示例。

图 10-18

Jenkins EC2 IAM 角色的权限配置

IAM 角色必须附加到所有 Jenkins 节点(控制器和 macOS 构建代理)。图 10-19 显示了一个将 EC2 IAM 角色附加到现有实例的例子。

图 10-19

将 IAM 角色附加到 Jenkins 实例

  • 应对所有使用中的 EC2 Jenkins 节点重复图 10-19 所示的示例流程。

可以从 Jenkins 实例中验证附加的 IAM 角色。清单 10-9 显示了如何验证正在使用的 AWS 身份的示例。响应中应返回附加的 IAM 角色名称。

$ aws sts get-caller-identity

{
    "UserId": "AROASOKDM2HTHY4MLKGHY:i-0846273b98d6d42f4",
    "Account": "123456789101",
    "Arn": "arn:aws:sts::123456789101:assumed-role/ec2-mac-role/i-0846273b98d6d42f4"
}

Listing 10-9Verifying AWS identity used on Jenkins instances

正在安装 AWS 代码管道 Jenkins 插件

要完成 Jenkins 环境设置,可以从 Jenkins 插件中心安装 AWS CodePipeline Jenkins 插件,如图 10-20 所示。

图 10-20

正在安装 AWS 代码管道 Jenkins 插件

在 AWS 控制台上设置代码管道

可以通过各种方式创建管道,如 AWS CLI、AWS SDKs、AWS 控制台等。我将使用 AWS 控制台来设置管道。从代码管道控制台开始创建管道,如图 10-21 所示。

图 10-21

从 AWS 控制台创建管道

对于管道设置,请提供一个名称并选择服务角色配置。在图 10-22 中,我选择为管道创建一个新的服务角色。

图 10-22

配置管道设置

接下来是配置管道的源阶段。在图 10-23 中,我配置了 CodeCommit 存储库,示例应用代码作为源代码存储在其中。

图 10-23

配置管道源阶段

在 AWS 控制台上创建管道时,source 之后的下一阶段默认为 Build ,并且不能修改。此外,一个管道必须包含至少两个阶段,所以在图 10-24 中,我配置了一个没有实际值的临时构建阶段来解决目前的限制。

图 10-24

配置临时构建阶段

有了源代码和构建阶段,我现在不需要任何进一步的阶段,所以我跳过部署阶段,如图 10-25 所示,继续创建管道。

图 10-25

跳过部署阶段创建

创建管道后,它会自动启动。由于构建阶段被配置为没有实际值的临时阶段,因此它将失败。您可以让管道失败,也可以停止管道执行。如图 10-26 所示,我停止了流水线执行。

图 10-26

创建后停止管道执行

既然已经建立了基本的管道,接下来,我将向您展示如何扩展这个管道,以便与 Jenkins 和 AWS Device Farm 集成,进行 iOS 应用测试。

使用 AWS CodePipeline 和 Jenkins 进行 Fastlane 测试

对于这种集成,您需要首先设置一个支持 CodePipeline 的 Jenkins 项目,然后在 CodePipeline 上配置一个自定义 stage 来与 Jenkins 项目集成。

配置 Jenkins 项目以测试应用

在这里,我将建立一个新的 Jenkins freestyle 项目,如图 10-27 所示,并将其配置为运行 Fastlane 测试。

图 10-27

为 Fastlane 测试创建 Jenkins 自由式项目

  • 记下项目名称,这将用于 AWS 代码管道的配置。

由于我正在为我的 Jenkins 环境使用控制器和构建代理架构,我将使用如图 10-28 所示的标签将此构建委托给我的 iOS 构建代理。

图 10-28

设置项目的运行位置

我们需要为源代码管理配置 AWS CodePipeline,因为该项目的源代码将由 AWS CodePipeline 传递。我的源代码管理设置如图 10-29 所示。

图 10-29

配置测试项目源代码管理

  • 应注意在 CodePipeline 操作类型配置中为 Provider 提供的值。这将在代码管道配置中使用。

构建触发器调用构建。为了确保在适当的时候调用构建,我将配置项目持续轮询 CodePipeline,以检查是否有任何与其配置匹配的挂起作业。如图 10-30 所示,我的项目将每分钟轮询一次 CodePipeline 中任何未完成的作业。

图 10-30

为项目配置生成触发器

Fastlane 测试的构建脚本如清单 10-10 所示。

set +x
echo "Set fastlane variables..."
export LC_ALL=en_US.UTF-8
export LANG=en_US.UTF-8

echo "Starting Fastlane..."
fastlane test

Listing 10-10Fastlane testing build script

该脚本应进入项目的构建命令部分,如图 10-31 所示。

图 10-31

输入项目的构建脚本

项目配置的最后一步是添加一个 AWS code pipeline Publisher post build 动作,如图 10-32 所示。提供一个工件名称来标识从 Jenkins 项目发布的任何工件。

图 10-32

配置 AWS 代码管道发布者生成后操作

  • 记下配置的工件名称,因为它将在代码管道配置中使用。
向代码管道添加自定义阶段

要将创建的 Jenkins 项目添加到 AWS 代码管道,请编辑创建的管道,如图 10-26 所示。删除之前配置的临时搭建阶段,添加一个新的阶段,如图 10-33 所示。

图 10-33

删除现有的生成阶段并将新阶段添加到管道中

在新增加的阶段,增加一个新的 Jenkins 测试动作,如图 10-34 所示。

图 10-34

向新阶段添加新的 Jenkins 测试行动

  • 必须在测试类别下选择添加 Jenkins 操作,以匹配 Jenkins 项目中的配置。

配置 Jenkins 测试操作。在配置 Jenkins 项目时,可以从提供和记录的值中检索提供者名称、项目名称和输出工件字段。在服务器 URL 字段中,输入 Jenkins 控制器的公共 IP 地址。图 10-35 显示了我的 Jenkins 测试动作的配置。

图 10-35

配置 Jenkins 测试操作

一旦配置了操作,您现在将拥有一个两阶段管道。要开始管道执行,您可以将新提交推送到 CodeCommit 存储库,或者在 CodePipeline 控制台上手动发布更改。

图 10-36 显示了 Fastlane 测试的成功管道执行。

图 10-36

Fastlane 测试的成功管道执行

关于 Jenkins 项目执行的更多信息,可以从 Jenkins 获取构建日志。清单 10-11 显示了在 Jenkins 上为图 10-36 所示的管道运行生成的日志片段。

**清单 10-11。**Fastlane 测试代码管道执行的日志片段

AWS 设备群集成

将真实 iOS 设备上的测试添加到您的开发过程中是势在必行的,我将演示如何将 AWS 设备场测试的阶段添加到现有的 CICD 管道中。类似于之前配置的 Fastlane 阶段,我将首先设置一个支持代码管道的 Jenkins 项目来构建应用,之后我将设置一个用于测试的 AWS 设备场项目,然后最后在代码管道上配置一个设备场阶段,并使用与 Jenkins 和 AWS 设备场集成的单独操作。

配置 Jenkins 项目以构建测试应用

要在 Device Farm 上测试 iOS 应用,需要 ipa 包。这里创建的项目将用于生成可测试的 ipa 包,以供设备场使用。

这需要创建一个新的 Jenkins freestyle 项目,如图 10-27 所示。我还将确保这个项目在我的 iOS 构建代理上运行,如图 10-28 所示。对于源代码控制管理,虽然它与之前创建的项目相似,但我会更改一些字段以适应当前的用例,如图 10-37 所示。

图 10-37

配置生成项目源代码管理

在图 10-37 中,CodePipeline 动作类型类别被设置为 build,因为该项目被配置为 Build 动作,并且它将在 CodePipeline 上被配置为 Build 动作。

  • 记下提供者和 Jenkins 项目名称,因为这将在代码管道配置中使用。

这个项目的构建触发器将像之前为 Fastlane 测试创建的项目一样进行配置,如图 10-30 所示。

清单 10-12 中显示了将用于这个项目的构建脚本。

set +x

export TEAM_ID=`aws secretsmanager get-secret-value --secret-id fastlane-secrets --query SecretString --output text | jq -r .TEAM_ID`
export KEYCHAIN_PASSWORD=`aws secretsmanager get-secret-value ​--secret-id fastlane-secrets --query SecretString --output text | jq -r .KEYCHAIN_PASSWORD`

security -v unlock-keychain -p "$KEYCHAIN_PASSWORD" "$HOME/Library/Keychains/login.keychain"
/usr/bin/xcodebuild build-for-testing -scheme SampleApp ​-destination generic/platform=iOS DEVELOPMENT_TEAM=$TEAM_ID ​-allowProvisioningUpdates -derivedDataPath $WORKSPACE
mkdir Payload && cp -r $WORKSPACE/Build/Products/Debug-iphoneos/SampleApp.app Payload/
zip -r Payload.zip Payload && mv Payload.zip SampleApp.ipa

Listing 10-12App build for test script

如构建脚本所示,团队 ID 和钥匙串密码是从 AWS Secrets Manager 中检索的。在构建应用以使用 xcodebuild 进行测试之前,钥匙串密码用于解锁钥匙串。

脚本应该进入项目的构建命令部分,如图 10-38 所示。

图 10-38

输入项目的构建脚本

项目配置的最后一步是添加一个 AWS code pipeline Publisher post build 动作,如图 10-39 所示。提供一个工件名称来标识从这个 Jenkins 项目发布的任何工件。

图 10-39

配置 AWS 代码管道发布者生成后操作

  • 记下配置的工件名称,因为它将在代码管道配置中使用。
设置 AWS 设备场

我将使用在第九章中创建的设备农场项目。将使用的项目如图 10-40 所示。

图 10-40

AWS 设备场项目

要提交测试运行,您还需要有一个设备池。在本例中,我将使用托管的“顶级设备”设备池来测试我的应用。为了从 AWS CodePipeline 中使用这个设备池,我必须提供设备池 ARN,因此为了获得 ARN,使用可以从图 10-40 中检索的项目 ARN,我将使用清单 10-13 中所示的 AWS CLI。

  • 注意图 10-40 中显示的设备群项目 ID 和清单 10-13 中运行命令返回的设备池 ARN,因为这些将在管道配置中使用。
$ aws devicefarm list-device-pools --arn arn:aws:devicefarm:us-west-2:123456789101:project:3973dab9-33ab-4d34-87c6-3e183e009b07 --region us-west-2 --query 'devicePools[?name==`Top Devices`]'

Listing 10-13Getting device pool ARN

将设备场阶段添加到管道

我已经配置了一个 Jenkins 项目,它可以构建我的测试应用并生成工件,我还有一个设备场项目可供使用。现在,我将向管道中添加一个新的阶段。

编辑管道并添加新阶段,使其成为三阶段管道。图 10-41 显示了我给新舞台起的名字。

图 10-41

添加设备场阶段

添加新阶段后,您将配置第一个操作。这将是一个 Jenkins 构建操作,它将连接到之前创建的 Jenkins 项目。图 10-42 显示了如何添加一个 Jenkins 构建动作。

图 10-42

添加 Jenkins 构建操作

  • 必须在 Build 类别下选择 Add Jenkins 操作,以便与 Jenkins 项目中的配置相匹配。

提供者名称、项目名称和输出工件字段可以从配置 Jenkins 项目时提供和记录的值中检索。在服务器 URL 字段中,输入 Jenkins 控制器的公共 IP 地址。图 10-43 显示了我的 Jenkins 构建动作的配置。

图 10-43

配置 Jenkins 构建操作

接下来,在同一阶段,我们将配置设备场操作。设备场操作不能与 Jenkins 操作并行运行,因为设备场依赖于 Jenkins 操作生成的工件。可以添加一个新的动作组,如图 10-44 所示。

图 10-44

添加设备场操作

设备农场动作可以如图 10-45 所示进行配置。应该选择 AWS 设备场作为测试提供程序。Jenkins 操作中配置的输出工件必须配置为该操作中的输入工件,以便工件可以交换。此处应提供从设备场检索的项目 ID 和设备池 ARN。AppType、输入工件中 ipa 文件的路径和 TestType 也是必填字段。

图 10-45

配置设备场操作

  • AWS 设备场仅在美国西部-2 地区(俄勒冈州)可用,因此这必须是在 Pipeline 操作中选择的地区。

配置完这两个动作后,您应该有一个如图 10-46 所示的阶段。

图 10-46

配置了两个操作的设备场阶段

一旦配置了操作,您现在将拥有一个三级管道。要开始管道执行,您可以将新提交推送到 CodeCommit 存储库,或者在 CodePipeline 控制台上手动发布更改。

图 10-47 显示了 Fastlane 测试和 AWS 设备群测试的成功流水线执行。

图 10-47

Fastlane 测试和设备群测试的流水线执行

可以从 Jenkins 检索应用构建的构建日志,同时可以从设备群控制台查看设备群测试结果的详细信息。

摘要

CICD 管道使您能够协调应用开发的不同步骤和组件,从源代码控制到用户部署。在这一章中,我用真实的例子展示了不同风格的 CICD 管道,比如 Jenkins 管道和 AWS CodePipeline,是如何被用来编排我们在前面章节中涉及的不同概念的。

在 Jenkins Pipeline 中,我们看到了如何测试、构建应用并将其交付到 App Store Connect。在 AWS CodePipeline 中,我们涵盖了不同的测试选项,同时还集成了 Jenkins。AWS CodePipeline 可以进一步扩展,以添加额外的阶段,如 Jenkins Pipeline。例如,在设备场测试后,您可以添加一个额外的阶段,与 Jenkins 集成以构建一个应用,用于与 Fastlane 一起分发,甚至将应用交付到 App Store Connect。