mcpField
U
upgrading-mwaa-environments

upgrading-mwaa-environments

github.com/aws云与 DevOps3.3k960 次安装1 天前更新
代码仓库

关于 upgrading-mwaa-environments

用于规划和执行 Amazon MWAA 环境 Airflow 升级的技能,面向需要升级版本并管理历史记录或环境标识的运维人员。它规划逐版本升级路径,并依据数据保留需求选择新建环境或原位升级策略。

主要功能

  1. 01规划有序版本升级路径
  2. 02支持跨越 Airflow 2.x 与 3.x
  3. 03按历史记录需求选择升级策略
  4. 04逐次确认版本变更并支持恢复

适用场景

  1. 01将 MWAA 升级到较新的受支持版本
  2. 02升级前在测试副本演练
  3. 03保留运行历史并原位升级

MCP 相关

可选地通过 AWS MCP 服务执行 AWS CLI 命令,以获得沙箱执行和审计日志;也可直接使用 AWS CLI。

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/aws/agent-toolkit-for-aws --skill upgrading-mwaa-environments

安装后在 Claude Code 中输入/upgrading-mwaa-environments确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

references/
airflow2-to-3-checklist.md
airflow2-to-3-quick-reference.md
discovery-preflight.md
mwaa-version-matrix.md
plan-materialization.md
strategy-blue-green-fresh.md
strategy-inplace-direct.md
strategy-inplace-green-validated.md
upgrade-engine.md

详细介绍

升级 MWAA 环境

AWS MCP 服务(可选,但建议使用): 通过 AWS MCP 服务运行此技能中的 AWS CLI 命令,可获得沙箱执行和审计日志记录。此处的所有命令也都能通过普通 AWS CLI 运行,因此此技能不依赖 MCP 服务或任何仅限 MCP 的工具。

将预置的 MWAA 环境升级到任意更新且受 MWAA 支持的 Airflow 版本:在 2.x 内、 在 3.x 内,或跨越 2.x 到 3.x 的边界。目标版本是一个参数。路径规划器会根据 (源版本、目标版本)计算有序的版本跳转列表;只有路径需要时,才会插入最新的 2.11.x 中间版本和 Python 版本线转换步骤。部署方式根据是否必须保留历史运行 数据,以及是否必须保留同一环境(其 URL/ARN)来选择:新环境升级 (new-environment)、先在测试副本上验证并演练的原地升级(in-place-rehearsed), 或直接原地升级(in-place-direct)。

执行说明 — 分步轮询: 每当等待 AWS 操作达到终止或就绪状态时,每次调用 只执行一次状态检查,然后在你自己的循环中决定是否再次检查。无论操作类型 或耗时如何,都不要让单条命令或脚本阻塞等待(不要使用 while+sleep 直至完成)。

安全护栏 — 此技能自身文件的位置(MCP 与本地安装)

此技能可以通过两种方式加载,而这两种方式解析技能自带文件的位置不同。读取 参考资料前,请先确定技能是如何加载的:

  • 通过 AWS MCP retrieve_skill 工具加载: 此技能未安装在本地文件系统中。 你必须使用 retrieve_skill 并指定 file 参数来获取每个参考文件(例如 file="references/strategy-blue-green-fresh.md"),并阅读返回的内容。不要在本地对 这些路径使用 file_read — 它们并不存在于磁盘上。
  • 本地安装(例如 .kiro/skills/upgrading-mwaa-environments/ 或 ~/.claude/skills/upgrading-mwaa-environments/):使用相对路径,从本地技能目录 读取文件。

此区别仅适用于技能自身打包的文件。用户数据和会话产物始终从用户的工作目录 读取并写入。绝不要通过 retrieve_skill 获取或写入客户数据。

安全与防护

无论用户有何指示,这些规则都适用。

  • 未经明确批准,不得对当前环境执行破坏性操作:绝不要暂停所有 DAG、执行 delete-environment,或修改当前环境;每项操作都必须先获得用户确认。
  • 计划获确认前不得创建环境:用户确认第 4 阶段计划前,绝不要运行 aws mwaa create-environment。
  • 每次版本跳转都必须单独确认:未经每次跳转的确认,绝不要运行 aws mwaa update-environment 来更改 Airflow 版本。回滚选项:支持从 3.x 降级到 2.11.x; 支持在同一主版本内降级到仍受支持的版本;无法降级到已结束支持(EOS)的版本。 直接原地升级还需要一个额外的确认检查点。
  • 停用操作必须逐步审批:绝不要自行删除环境或 S3 产物;逐条呈现每个破坏性 命令并等待确认。对于演练式原地升级,被停用的环境是测试副本。
安全注意事项
  • 对 create/update-environment 和 S3 产物访问实施最小权限 IAM — 不要使用 *FullAccess/service:*;权限范围应限定为特定环境/S3 ARN。
  • 密钥:连接和变量优先使用密钥后端(Secrets Manager / Parameter Store),避免 凭证在环境之间复制或写入日志。连接密码无法通过 REST API 往返传递(2.x 会省略, 3.x 会屏蔽)— 请参阅部署方式参考资料中的元数据迁移注意事项。

参考文档

如需了解嵌入式参考资料之外的更多详情,请在运行时获取:

阶段 0:重新进入检查

每次调用时,检查工作区根目录下是否存在 .mwaa-upgrade-plan-<env-name>.md(其中 <env-name> 是源环境名称)。并行升级多个环境时,可以同时存在多个计划文件。

  • 如果存在:加载计划,报告源环境、目标环境、所选方案以及位置 (working_on, current_jump, jump_status)。从记录的环境、版本跳转索引和状态恢复引擎(upgrade-engine.md):upgrading -> 重新轮询或重新发出待处理的 update-environment;validating -> 针对该版本跳转重新运行条件式验证/扫描/修复循环。对于新建环境,起始版本验证窗口(版本跳转 1 的 to)对应 current_jump: 1, jump_status: validating;从该处恢复。如果未设置位置,则从下一个未完成的阶段恢复。 **在越过任何必需检查点之前,重新读取计划中的检查点状态;如果尚未满足,则逐字重新阅读方案参考中的该检查点章节——不要依据记忆中的摘要采取行动。**具体而言:版本跳转全部完成并不表示测试运行检查点已通过。如果 ## Test-run results 部分中有任何一行的状态不是 SUCCESS 或 NEEDS-DECISION,则该检查点为 NOT-PASSED,并会阻止切换(新环境升级)、正式升级(演练过的原地升级——请参阅 live_upgrade_allowed)或完成(直接原地升级),无论版本跳转状态如何,也无论 has_import_errors: false 与否。同样,如果某批次的 Monitored one full cycle? 单元格不是 yes,则表示尚未完全切换。

**重新加载规则(每次恢复时):**在会话开始时以及每次上下文压缩后,采取任何行动前都要重新读取整个 .mwaa-upgrade-plan-<env-name>.md;绝不可依据记忆中或摘要中的版本采取行动。此规则将上述针对必需检查点的记忆摘要规则扩展至整个计划。

**计划协调(恢复任何工作前执行):**验证计划的自洽性不变量(完整列表见 plan-materialization.md):标记为 PASSED 的检查点要求其所有前置 Checklist 项均为 DONE/SKIP;Where we are.test_run_checkpoint 必须与 Test-run results 表一致(任何 PENDING 行 -> NOT-PASSED);current_jump/jump_status 必须与 Upgrade Path Status 一致;对于演练过的原地升级,live_upgrade_allowed 必须与测试运行检查点一致。出现任何不一致时,切勿信任计划——通过只读方式使用 aws mwaa get-environment、/dags 和 /health 确认实际状态,修复计划以符合实际情况,并在 Last updated 中记录。如果实际情况不明确,请询问用户。否则,过期的 PASSED 检查点会触发破坏性后续操作:切换并删除当前环境(新环境升级)、执行不可逆的正式升级(演练过的原地升级)或过早完成(直接原地升级)。

  • 如果不存在:继续执行阶段 1。

第 1 阶段:发现环境 + 规划路径 + 预检

获取并遵循 references/discovery-preflight.md。该文档会执行 环境发现、预检安全检查、版本矩阵更新、目标选择和升级路径规划(仅检测——版本跳转 将在第 7 阶段执行)。现在将每项输出记录到计划中,因为第 4 阶段会将其写入持久化的 计划文件;否则,如果在第 4 阶段之前压缩上下文,这些信息就会丢失:

  • 发现的环境事实(Airflow 版本、S3 构件路径和对象版本、 KmsKey、配置选项、DAG 列表、共享实用程序);
  • 新环境/测试副本的 S3 布局(新环境升级、 预演式原位升级);
  • 预检结果(S3 版本控制、目标约束、KMS 密钥策略复用);
  • 选定的目标版本;
  • 有序升级路径——每次版本跳转的 from/to/python_change/crosses_major,仅当源版本不是 2.11.x 时, 才插入最新的 2.11.x 过渡版本以及 Python 转换步骤。

第 2 阶段:选择升级方式

一个问题决定最安全的方式;是否保留同一个环境(其 URL 和 ARN)是由此决定的结果, 并非单独的选择。

你需要保留运行历史吗——也就是 Airflow UI 中过往 DAG 运行和任务实例的记录?

  • 不需要,可以从头开始 → 新环境升级(slug new-environment)。在目标版本上创建一个全新环境并切换过去。新环境有新的 URL, 且没有运行历史;切换之前,当前环境会照常运行,因此回滚只需“不切换”。
  • 需要,保留历史 → 预演式原位升级(slug in-place-rehearsed)。 原位升级现有环境——URL 不变,完整保留历史。首先在临时测试副本上预演整个升级, 以便安全地发现问题。只有当用户明确拒绝预演时,才使用直接原位升级(不使用测试副本)。

是否保留同一个环境(其 URL 和 ARN)是结果,而非输入:保留运行历史意味着原位升级当前 环境(URL 和 ARN 不变);而新环境升级必然会创建新环境。本技能不会将新环境(新的 URL 和 ARN)与保留运行历史结合起来——如果用户需要新的 URL 和 ARN,运行历史将不会迁移 (新环境升级)。

方式历史记录URL/ARN升级期间的当前环境创建新环境/测试环境的时间推荐
新环境升级不保留新的持续运行,直到切换第一次版本跳转的目标版本是
预演式原位升级保留(快照)相同先在测试副本上预演,然后升级第一次版本跳转的目标版本是
直接原位升级保留(快照)相同直接升级无否

新环境升级和预演式原位升级都值得推荐;用户根据需求选择。直接原位升级不推荐, 且需要额外的确认检查点。

安全防护:如果用户选择预演式或直接原位升级,且环境中有超过 50 个 DAG,或在 第 3 阶段检测到复杂 DAG,请发出警告并改为推荐新环境升级。不要阻止用户继续。

在计划中记录所选方式的 slug(new-environment/in-place-rehearsed/ in-place-direct)。位置(working_on、current_jump、jump_status)最初留空。

阶段 3:评估兼容性

  1. 兼容性扫描(仅在跨主版本升级时执行): 如果计划中的任何版本跳跃标记为 crosses_major,请将使用 AIR 规则运行 Ruff 作为必需检查点:

    ruff check --preview --select AIR .
    

    按规则代码分组报告发现的问题。Ruff AIR 规则覆盖 2 到 3(AIR301/302/303/311/312)和 3.1(AIR321,预览版)的 API 变更;它们不会检测 2.x 次要版本之间移除的 API,也不会检测 3.2 特有的变更。引擎中的跨版本前弃用警告扫描涵盖 2.x 内部的差异。如果没有任何版本跳跃标记为 crosses_major(即主版本内升级),AIR 扫描仅供参考;请改为依赖引擎通用的 DeprecationWarning 扫描。

    AIR311 的版本感知严重性(导入路径迁移): AIR311 会标记迁移到 airflow.sdk 的导入(例如,airflow.datasets.Dataset -> airflow.sdk.Asset)。严重性取决于目标版本:

    • 目标为 3.0.x 或 3.1.x: 软弃用——存在兼容性垫片,旧导入仍可使用。建议修复,但不构成阻塞。
    • 目标为 3.2.1+: 硬性阻塞项——垫片模块已完全移除(解析时出现 ModuleNotFoundError)。部署到目标版本前必须修复。

    当目标版本为 3.2.1+ 时,始终将 AIR311 分类为“必须修复”。修复后的代码(from airflow.sdk import ...)仅适用于 AF3,跨主版本升级完成前无法部署——请参阅各方案参考中的两阶段部署说明。

    Ruff 无法检测的模式: 手动检查清单(airflow2-to-3-checklist.md)包含 Ruff 无法识别的模式,尤其是 _TaskDecorator.output(第 15 节)和 BashOperator 中的独立 Airflow CLI(第 16 节)。即使 Ruff 未报告任何问题,也始终要运行手动扫描。

  2. 使用 airflow2-to-3-checklist.md 中的模式运行手动扫描。
    提示元数据数据库警告(所有方案,仅限跨主版本升级): 使用元数据数据库访问的 DAG 在 AF2 上可运行,但在 AF3 上会失效。分为两类:

    • ORM 访问型 DAG(settings.Session()、provide_session——清单第 1 节):必须重新设计为使用 REST API(VPC 内的 Web 服务器,或速率限制较低的 invoke-rest-api),或者删除。
    • CLI 访问型 DAG(BashOperator 中的 airflow db ...——清单第 16 节):重新设计为使用 MWAA CLI 端点(优先,较简单)、REST API,或者删除。确定采用此方案前,Agent 必须通过 cheat-sheet 验证目标版本中命令是否可用。 在计划(阶段 4)中记录每个受影响的 DAG 及其类别。
  3. 根据 mwaa-version-matrix.md 中目标版本的约束检查 requirements.txt。

  4. 密钥后端检测: 检查环境配置中是否覆盖了 secrets.backend 配置。如果使用 Secrets Manager 或 Parameter Store,请注明变量和连接为外部配置,无需迁移。如果未配置密钥后端,请在制定计划时向用户说明: 连接密码不会通过 REST API 迁移(2.x 会省略,3.x 会屏蔽),因此必须将含密码的连接迁移到密钥后端,或者在目标环境中通过其他方式重新输入密码;此外,迁移期间变量值和连接 extra 会经由 --body/CLI 参数传递,并出现在审计日志中。请在计划(阶段 4)中记录这一点。

  5. 插件清单: 如果设置了 PluginsS3Path,请列出其中的内容。标记需要特别注意的 FAB/网页视图插件。

  6. 按升级难度对每个 DAG 分类:简单、中等、复杂。

  7. 将 DAG 分批处理(共享实用工具放在第 0 批,先处理简单项)。

阶段 4:生成升级计划

将 .mwaa-upgrade-plan-<env-name>.md 写入工作区根目录(其中 <env-name> 是在阶段 1 中发现的源环境名称)。包括:

  • 环境详情(源名称、源版本、区域),以及目标版本和完整的升级路径表
  • 选定的方法——其标识符(new-environment / in-place-rehearsed / in-place-direct)
  • 升级前检查结果(S3 版本控制、约束、升级路径状态)
  • Docker 验证状态(待处理)
  • 密钥后端检测结果
  • 需要进行的 requirements 变更
  • 仅限跨主版本升级 — 元数据数据库访问检查结果(阶段 3 步骤 2):每个使用元数据数据库访问的 DAG,按 ORM 访问(重构为 REST API 或移除)或 CLI 访问(重构为 MWAA CLI 端点、REST API 或移除)分类;以及一个待处理的修复后重新扫描检查点(阶段 5 步骤 2d)。如果没有任何版本跳跃属于 crosses_major,则省略此项。
  • 方法特定字段:新环境升级 — 新环境名称、新环境 S3 路径;演练式原地升级 — 测试副本环境名称、测试副本 S3 路径;直接原地升级 — 无(原地升级,不创建新环境或测试环境)
  • 包含每个 DAG 状态的批次表

将有序升级路径记录为 ## Upgrade Path 表:

跳跃从 → 到Python 变更新主版本状态
1<jump.from> → <jump.to>是/否是/否待处理

对于演练式原地升级(两个环境——先测试副本,再升级当前环境),升级路径表应分别包含 Test copy status 和 Your current environment status 列;将当前环境的版本跳跃标记为 IRREVERSIBLE。

将每个检查点和记录部分写入计划(必须)

在展示计划前,遵循 plan-materialization.md,生成其中规定的每个部分,并符合其 ## Plan format contract(固定状态词汇、Last updated 行、## Where we are 键),满足其 ## Self-consistency invariants:## Checklist、每种方法所需的独立 PENDING 检查点条目、预先填充的 ## Test-run results 和 ## Switchover progress(新环境升级)表、## Where we are 块、区分解析通过与测试运行验证的状态词汇(LOADED 与 VERIFIED),以及方法特定的子步骤拆分规则。计划是持久且可恢复的事实来源:任何只在方法参考资料中以散文形式出现、而未在此处列为独立 PENDING 条目的步骤,在上下文压缩后都将被跳过。展示计划前先生成这些部分;解析通过信号(has_import_errors: false)不满足测试运行检查点,绝不可记录为“已验证”或“已完成”。

继续操作前,将计划呈现给用户确认。

阶段 5:修复代码

  1. 批次 0(共享工具): 先修复共享模块。应用 airflow2-to-3-quick-reference.md 中的模式。
  2. 每个批次(1..N): a. 运行 Ruff 自动修复:ruff check --preview --select AIR --fix --unsafe-fixes <files> b. 根据 airflow2-to-3-quick-reference.md 修复每个 DAG 的剩余问题。 c. 重新运行 Ruff,确认没有 AIR 违规。 d. 重新扫描已修复的文件(仅限跨主版本升级),使用清单第 15/16 节中的 grep 命令,而不只是扫描原始文件。--unsafe-fixes 可能会将 xcom_pull 模板重写为 <task>.output(在 AF3 中无效),却不再报告该问题;否则,这种运行时错误只会在实时解析时显现。修复扫描命中项并重新运行 Ruff。 e. 保存进度(参见 plan-materialization.md 中的 ## Saving progress):将每个 DAG 标记为 DONE,并在开始下一批之前更新 Last updated。

阶段 6:通过 Docker 进行本地验证(建议;跨大版本时强烈建议)

使用 aws/amazon-mwaa-docker-images,根据目标版本验证修复后的代码。此仓库仅提供 Airflow 2.9.2 及更高版本的映像。如果目标版本低于 2.9.2,请跳过 Docker 验证(在计划中记录跳过原因),改为依赖阶段 7 中引擎的实时验证。

跨大版本升级: 如果任何版本跳跃属于 crosses_major,则强烈建议进行 Docker 验证。Ruff AIR 规则会漏掉若干可能导致运行时故障的模式(例如,移除了 _TaskDecorator.output,以及在 3.2.1+ 中完全移除的兼容垫片模块)。Docker 导入验证可以在实时部署前捕获这些问题,避免在远程环境中反复经历修复、部署、失败的循环。如果跨大版本升级时跳过 Docker 验证,请记录跳过原因,并警告可能存在未检测到的运行时错误。

  1. 检查 Docker 是否可用:docker --version。如果不可用、用户拒绝,或目标版本 < 2.9.2,请在计划中记录跳过及警告,并继续阶段 7。
  2. 拉取目标版本映像(在运行时从仓库 README 中解析确切的标签)。
  3. 验证 requirements.txt:将文件挂载到容器中,运行安装验证。如果失败:报告冲突并建议修复,循环执行直至通过或用户选择跳过。
  4. 验证 plugins.zip(如果存在):将其挂载并运行导入验证。
  5. 验证 DAG 导入:挂载修复后的 DAG 并运行解析。如果有错误:返回阶段 5 处理受影响的文件,然后重新验证。
  6. 保存进度:将 Docker 验证标记为 DONE 或 SKIP(并注明原因),然后更新 Last updated。

阶段 7:部署

根据计划中选择的方案,转到相应的参考文档。每种方案都会在新环境(新环境升级)、测试副本后再到当前环境(经过演练的原地升级),或当前环境(直接原地升级)上执行分步升级 (upgrade-engine.md)。

在整个阶段 7 中,按照 plan-materialization.md 中的说明保存进度:在每个步骤、子步骤、版本跳跃、检查点、批次和每个 DAG 状态之后,以及执行下一项操作之前,将变更(状态标记 + 持久事实 + Last updated + ## Where we are)保存到计划中。

DAG 测试运行验证(每种方案都必须执行): 引擎的解析验证通过后,对每个 DAG 或每批 DAG 调用 skill testing-mwaa-workflow,确认其在目标版本上成功执行。如果失败,调用 skill debugging-mwaa-workflow 进行根因分析。不要通过 REST API(POST /dags/{id}/dagRuns)手动触发 DAG 运行,也不要内联轮询运行状态——触发、轮询、分类(SUCCESS 与 NEEDS-DECISION)及修复循环均由测试 skill 负责。在测试运行验证通过之前,不要继续执行实时升级(经过演练的原地升级)、切换(新环境升级)或完成操作(直接原地升级)。这是必需的检查点。仅解析验证(无导入错误)并不能证明工件可以正确运行。每种方案的参考文档都将此定义为一个明确编号的必需检查点,必须在进入下一阶段前完成:新环境升级第 4 步检查点、经过演练的原地升级第 1 步检查点、直接原地升级第 4 步。该检查点要求在计划的 ## Test-run results 部分记录结果。升级 skill 负责协调此循环,不会内联执行测试或调试逻辑。

阶段 8:切换 + 退役

适用于会创建新环境/测试环境的方法(新环境升级、演练式原地升级)。直接原地升级会在阶段 7 完成(不创建新环境/测试环境)。

  • 新环境升级:从当前环境切换到新环境,然后停用当前环境。
  • 演练式原地升级:测试副本仅用于演练;当前环境就是已完成升级的线上环境。跳过切换;停用测试副本。不要删除当前环境。
  1. 分批切换(仅限新环境升级;每批都需明确批准):对每个批次(与阶段 7 中相同的批次分组一致): a. 暂停当前环境中该批次的 DAG(调用之间间隔较短;invoke-rest-api 有 10 秒超时和 6 MB 响应上限 (文档) ,且会按环境限流,因此应采用退避策略,而非固定速率):

    aws mwaa invoke-rest-api --name <current-env> --method PATCH \
      --path /dags/<dag_id> --body '{"is_paused":true}'
    sleep 0.2
    

    b. 排空:等待当前环境中这些 DAG 的运行中实例完成。轮询 dag_runs,检查 state=running。如果某个运行实例在 10 分钟后仍未完成,且其活动任务是传感器或延迟执行的运算符(检查 task_instances 中是否有 state=deferred 或 state=sensing),则继续在新环境中取消暂停。当前环境中已暂停的 DAG 不会产生新的运行实例;运行中的实例会独立完成或超时。它不会与新环境冲突,因为新环境是全新的,其中没有该 execution_date 的既有 dag_runs。 c. 在新环境中取消暂停相同的 DAG(同样每次调用间隔 0.2 秒):

    aws mwaa invoke-rest-api --name <new-env> --method PATCH \
      --path /dags/<dag_id> --body '{"is_paused":false}'
    sleep 0.2
    

    d. 在继续下一个批次之前,先在新环境中监控当前批次,至少覆盖一个完整的调度周期。如果失败,则在当前环境中重新启用该批次(回滚)并进行调查。

    上述按 DAG 暂停/取消暂停的操作使用 --path /dags/<dag_id>,受 64 字符的 --path 长度限制;对于长度超限的 dag_id,请使用 Airflow CLI 备用方案(aws mwaa create-cli-token -> airflow dags pause|unpause),详见 upgrade-engine.md。

    对于小型环境(DAG 少于 10 个,或全部被归类为简单),如果用户更看重速度,可提供一次性全部切换的替代方案。

  2. 稳定期:最终批次完成后的用户自定义监控时段 (建议:24h,或最长时间间隔的 DAG 的一个完整周期)。 在环境内置的 CloudWatch 指标控制面板和 Create recommended alarms 操作中 监控环境健康状况;它们会保持最新。调度器运行状态(例如 Scheduler heartbeat)和队列积压(例如 Oldest queued task age)是切换后需要优先关注的核心指标 (https://docs.aws.amazon.com/mwaa/latest/userguide/monitoring-dashboard.html)。 对于新环境升级,当前环境会保持暂停状态,而不会被删除。对于经过演练的原地升级,当前环境处于运行状态并接受监控。

  3. 退役(每个步骤都需明确批准):

    • 新环境升级:删除当前环境 — aws mwaa delete-environment --name <current-env>;归档其 S3 成品(可选)。
    • 经过演练的原地升级:删除测试副本 — aws mwaa delete-environment --name <test-copy>。
  4. 只读选项(新环境升级):提供在最终删除前将当前 环境继续保持暂停一段较长时间的选项,以便访问历史 UI。

  5. 保存进度:将升级标记为已完成(状态 DONE),设置 Where we are phase: complete,并更新 Last updated。

评论

支持 Markdown