Automate CloudKit tests with cktool and declarative schema
Developer Tools 高级 18m

使用 cktool 和声明式 Schema 自动化 CloudKit 测试

Automate CloudKit tests with cktool and declarative schema

2021年6月10日

在 Apple 官方观看视频

一句话判断

cktool 让 CloudKit 的 Schema 管理终于可以进入 CI/CD 管道了 —— 声明式 JSON 配置 + 命令行工具 = Schema 变更可审计、可回滚、可自动化,这是 CloudKit 从”玩具级后端”迈向工程化工具的关键一步。

这场 Session 讲了什么

Session 深入讲解了 cktool 命令行工具和声明式 Schema 管理系统。cktool 是苹果在 macOS 12 中引入的 CloudKit 管理命令行工具,支持 Schema 操作、数据查询、环境管理等。

核心能力:

  1. Schema 导出/导入cktool schema export 把当前 Schema 导出为 JSON 文件,cktool schema apply 从 JSON 文件应用 Schema 变更。
  2. Schema 推送cktool schema promote 把开发环境的 Schema 推送到生产环境。
  3. 数据操作cktool record query 查询记录、cktool record delete 删除记录。
  4. 环境管理:查看容器列表、切换开发/生产环境。

声明式 Schema 的工作流:在 JSON 文件中定义 Record Type、Field、Index -> 用 cktool schema apply 应用到开发环境 -> 测试验证 -> cktool schema promote 推送到生产环境。整个过程可以集成到 CI/CD 管道。

值得深挖的点

Schema 变更的安全机制。CloudKit 的 Schema 变更有严格的安全限制:只能在开发环境中添加/修改 Field 和 Index,生产环境只能添加(不能删除或修改已有字段)。cktool schema apply 在执行前会做 diff,只应用变更的部分。如果变更涉及删除字段(开发环境),会要求二次确认。

cktool 的认证方式cktool 使用 Apple ID 的 API Token 认证,不是 iCloud 登录。你需要在 App Store Connect 中生成一个 API Key(Issuer ID + Key ID + Private Key),然后用 cktool auth 登录。这个 token 可以设过期时间,适合 CI 环境。注意:token 权限继承生成它的 Apple ID 的权限,建议用一个专用的开发者账号生成 CI token。

代码片段

完整的 CI Schema 管道

# 步骤 1:认证
cktool auth \
    --api-token "$(cat /secure/ck-api-token.json)"

# 步骤 2:导出当前开发环境 Schema 作为基线
cktool schema export \
    --container iCloud_com.myteam.myapp \
    --environment development \
    > schema_baseline.json

# 步骤 3:应用新 Schema(从代码仓库中的声明文件)
cktool schema apply \
    --container iCloud_com.myteam.myapp \
    --environment development \
    --input schema/schema_v2.json \
    --force  # 跳过交互确认(CI 模式)

# 步骤 4:运行集成测试(验证新 Schema 是否正常工作)
xcodebuild test \
    -scheme MyAppIntegrationTests \
    -destination 'platform=iOS,name=iPhone 13'

# 步骤 5:测试通过后,推送 Schema 到生产环境
if [ $? -eq 0 ]; then
    cktool schema promote \
        --container iCloud_com.myteam.myapp
    echo "Schema 已推送到生产环境"
fi

声明式 Schema JSON 示例

{
    "recordTypes": {
        "UserProfile": {
            "fields": {
                "nickname": { "type": "STRING" },
                "avatarURL": { "type": "STRING" },
                "level": { "type": "INT64" },
                "lastActiveDate": { "type": "DATE/TIME" },
                "isPremium": { "type": "INT64" }
            },
            "indexes": {
                "nicknameIndex": {
                    "field": "nickname",
                    "type": "VALUE"
                },
                "levelSortIndex": {
                    "field": "level",
                    "type": "SORTABLE"
                },
                "lastActiveQueryIndex": {
                    "field": "lastActiveDate",
                    "type": "QUERYABLE"
                }
            }
        }
    },
    "zones": {
        "_defaultZone": {
            "capabilities": []
        }
    }
}

查询和清理测试数据

# 查询开发环境中的测试记录
cktool record query \
    --container iCloud_com.myteam.myapp \
    --environment development \
    --record-type UserProfile \
    --filter 'level < 5' \
    --limit 100

# 清理测试数据(仅开发环境)
cktool record delete \
    --container iCloud_com.myteam.myapp \
    --environment development \
    --record-name "test_user_001" \
    --force

# 批量清理(结合 jq 处理查询结果)
cktool record query \
    --container iCloud_com.myteam.myapp \
    --environment development \
    --record-type UserProfile \
    --filter 'nickname BEGINS_WITH "test_"' \
    | jq -r '.records[].recordName' \
    | xargs -I {} cktool record delete \
        --container iCloud_com.myteam.myapp \
        --environment development \
        --record-name {}

最佳实践

  1. Schema JSON 文件纳入 Git 版本控制。每次 Schema 变更都是一个 commit,PR 中可以 code review 数据模型变更。
  2. CI 中先 schema apply 到开发环境,跑完测试后再 schema promote。不要跳过测试直接推生产,CloudKit Schema 变更不可逆。
  3. 用专用 Apple ID 生成 CI token。不要用个人账号的 token,离职时无法收回权限。专用账号只给 CloudKit 相关权限,遵循最小权限原则。

还有什么值得关注

  • cktool 目前只在 macOS 上可用,不支持 Linux(CI 可以用 macOS runner)。
  • Schema 的 JSON 格式和 CloudKit Console 中导出的格式一致,可以互相转换。
  • cktool 支持输出 JSON 格式的结果,方便在脚本中用 jq 处理。
WWDC 2021