ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

moto 中的 AWS CodeDeploy 模拟:已实现 API 清单、源码实现解析与 boto3 测试实战

moto 中的 AWS CodeDeploy 模拟:已实现 API 清单、源码实现解析与 boto3 测试实战 Mock测试【免费下载链接】motoA library that allows you to easily mock out tests based on AWS infrastructure.项目地址https://gitcode.com/gh_mirrors/mo/moto点击查看免费下载本文以 moto 官方服务文档codedeploy.rst的核心内容——CodeDeploy 服务已实现/未实现的 API 清单为骨架结合 moto/codedeploy/models.py、moto/codedeploy/responses.py 等源码与 tests/test_codedeploy/test_codedeploy.py 测试用例讲解 moto 如何 mock CodeDeploy 的 Application、DeploymentGroup、Deployment 三层资源模型读者可以据此判断自己的 CodeDeploy 测试代码能否被 moto 覆盖并直接复用可运行的测试代码与错误码断言。文档定位CodeDeploy 的 API 覆盖情况moto 的服务文档位于 docs/docs/services/codedeploy.rst它通过autoclass引用moto.codedeploy.models.CodeDeployBackend展示后端接口并给出该服务的 Implemented features for this service 清单。根目录的 IMPLEMENTATION_COVERAGE.md 也给出了同一结论CodeDeploy 约有 29% 的 API 已实现。已实现的 16 个 API以下 API 在当前仓库中均有对应实现是编写本地测试可以依赖的能力边界API作用后端实现位置create_application创建 CodeDeploy 应用支持 Server / Lambda / ECS 计算平台与 tagsmodels.pycreate_deployment_group为应用创建部署组含服务角色、部署配置、蓝绿配置等字段models.pycreate_deployment在部署组下创建一次部署revision 支持 S3 / GitHub / Stringmodels.pyget_application按名称查询应用详情models.pybatch_get_applications批量查询多个应用models.pyget_deployment按 deploymentId 查询部署详情models.pybatch_get_deployments批量查询部署models.pyget_deployment_group按应用名 部署组名查询部署组models.pylist_applications列出全部应用名源码标注分页尚未实现models.pylist_deployments按应用/部署组/状态过滤列出部署 IDmodels.pylist_deployment_groups列出指定应用下的部署组名models.pytag_resource给资源 ARN 打标签models.pyuntag_resource按 tag key 移除标签models.pylist_tags_for_resource查询资源 ARN 的标签列表models.py尚未实现的 API写测试前需要知道的限制以下 API 在文档清单中标记为未勾选- [ ]如果你的测试依赖它们当前版本的 moto 无法模拟部署配置管理create_deployment_config、get_deployment_config、delete_deployment_config、list_deployment_configs删除类操作delete_application、delete_deployment_config、delete_deployment_group、delete_git_hub_account_token、delete_resources_by_external_id修订版本管理register_application_revision、get_application_revision、list_application_revisions、batch_get_application_revisions实例级别查询batch_get_deployment_instances、get_deployment_instance、list_deployment_instances、batch_get_deployment_groups、batch_get_deployment_targets、get_deployment_target、list_deployment_targets混合/本地实例管理add_tags_to_on_premises_instances、batch_get_on_premises_instances、get_on_premises_instance、list_on_premises_instances、register_on_premises_instance、deregister_on_premises_instance、remove_tags_from_on_premises_instances部署生命周期控制continue_deployment、stop_deployment、put_lifecycle_event_hook_execution_status、skip_wait_time_for_instance_termination更新类操作update_application、update_deployment_group账户 Token 列表list_git_hub_account_token_names。此外源码中 models.py 的list_applications与 models.py 的list_deployment_groups均带有TODO: implement pagination注释list_deployments的externalId、createTimeRange参数虽然会被解析见 responses.py但从源码结构看后端过滤逻辑目前主要实现了应用名/部署组名/状态过滤时间范围过滤尚未生效——使用时应意识到这是与真实 AWS 行为的差异。资源模型Application / DeploymentGroup / Deployment 三层结构从源码结构看moto 的 CodeDeploy 模拟完整遵循了 AWS 的资源层级定义在 moto/codedeploy/models.py 中Applicationmodels.py#L23-L46持有applicationIdUUID4 生成、applicationName、computePlatform、createTimeISO-8601 毫秒时间戳。源码注释特别说明 Boto 文档说该字段是 datetime而 AWS API 返回的是字符串数值格式moto 按 AWS 行为用iso_8601_datetime_with_milliseconds()生成。DeploymentGroupmodels.py#L71-L142字段覆盖很全包括ec2TagFilters、onPremisesInstanceTagFilters、autoScalingGroups、serviceRoleArn、triggerConfigurations、alarmConfiguration、autoRollbackConfiguration、deploymentStyle、blueGreenDeploymentConfiguration、loadBalancerInfo、ecsServices、terminationHookEnabled等。其中targetRevision、lastSuccessfulDeployment、lastAttemptedDeployment在to_dict()中是# TODO占位空字典意味着这些字段目前返回空结构而非真实数据。DeploymentInfomodels.py#L145-L236新建部署的status固定为CreatedstartTime/completeTime初始为NonedeploymentOverview各状态计数Pending / InProgress / Succeeded / Failed / Skipped / Ready初始全为 0creator默认为user。默认部署配置的回退逻辑CodeDeployBackend.create_deployment_group中有一个值得注意的行为如果调用方没有传deploymentConfigName会回退到内置默认值CodeDeployDefault.OneAtATimemodels.py#L416-L418。该枚举models.py#L49-L56内置了 AWS 官方三种部署配置AllAtOnce、HalfAtATime、OneAtATime。进一步看create_deploymentmodels.py#L351-L355若部署时未显式指定deploymentConfigName会继承所属部署组的配置名。因此在 mock 环境下get_deployment返回的deploymentConfigName会是字符串形式枚举的__str__会返回CodeDeployDefault.OneAtATime这样的类限定名测试中若断言该字段需留意这一点。校验规则与错误码moto 对创建操作做了与 AWS 一致的语义校验所有异常定义在 moto/codedeploy/exceptions.py均为 400 状态的JsonRESTError触发场景异常类错误码应用/部署/部署组不存在ApplicationDoesNotExistException、DeploymentDoesNotExistException、DeploymentGroupDoesNotExistException重复创建同名应用/部署组ApplicationAlreadyExistsException、DeploymentGroupAlreadyExistsExceptioncreate_deployment缺少deploymentGroupNameDeploymentGroupNameRequiredExceptionmodels.py#L346-L349list_deployments只传了applicationName未传部署组名DeploymentGroupNameRequiredExceptionmodels.py#L467-L470list_deployments只传了deploymentGroupName未传应用名ApplicationNameRequiredExceptionmodels.py#L472-L475这些错误码是测试断言的直接依据后文测试示例会用到。请求路由从 boto3 调用到后端的调用链理解 mock 如何拦截请求可以看 moto/codedeploy/urls.pyURL 基础匹配https?://codedeploy\.(.)\.amazonaws\.com即 boto3 客户端发往codedeploy.region.amazonaws.com的 JSON 请求会被 moto 接管大多数 API 走CodeDeployResponse.dispatch通用分发按操作名路由到对应 handlerlist-tags-for-resource、tag-resource、untag-resource三个操作走独立的 URL 路径{0}/tag-resource$等这是因为它们属于 CodeDeploy 的 REST JSON 协议而非 query 协议。handler 实现位于 moto/codedeploy/responses.pyCodeDeployResponse的codedeploy_backend属性按current_account region 从codedeploy_backendsBackendDict(CodeDeployBackend, codedeploy)models.py#L531取出后端实例。每个 handler 通过self._get_param(...)从 JSON body 中取参如 create_deployment handler 解析了applicationName、deploymentGroupName、revision、description、autoRollbackConfiguration等全部参数调用后端方法后以 JSON 字符串返回Content-Type 固定为application/json。测试实战可复制运行的完整示例以下示例全部取自仓库内 tests/test_codedeploy/test_codedeploy.py配合mock_aws装饰器即可本地运行覆盖了已实现 API 的典型用法。1. 创建应用与查询import boto3 from moto import mock_aws mock_aws def test_create_application(): client boto3.client(codedeploy, region_nameap-southeast-1) for platform in [Server, Lambda, ECS]: name ftest-application-{platform} response client.create_application( applicationNamename, computePlatformplatform, tags[{Key: Name, Value: Test}], ) assert applicationId in response resp client.get_application(applicationNamename) application resp[application] assert application[applicationId] response[applicationId] assert application[applicationName] name assert application[computePlatform] platform assert createTime in application重复创建同名应用会返回ApplicationAlreadyExistsException对应 test_create_application_existing。2. 创建部署组与三种 revision 类型create_deployment的revision参数是字典revisionType可以是S3、GitHub、String三种moto 会原样存储并在get_deployment时返回对应 test_create_deployment_revision_s3、test_create_deployment_revision_github、test_get_deploymentmock_aws def test_create_deployment_revision_s3(): client boto3.client(codedeploy, region_nameus-west-2) application_name mytestapp deployment_group_name test-deployment-group service_role_arn arn:aws:iam::123456789012:role/CodeDeployDemoRole client.create_application(applicationNameapplication_name, computePlatformServer) client.create_deployment_group( applicationNameapplication_name, deploymentGroupNamedeployment_group_name, serviceRoleArnservice_role_arn, ) revision_S3 { revisionType: S3, s3Location: { bucket: my-bucket, key: my-key, bundleType: zip, version: 1, eTag: my-etag, }, } response client.create_deployment( applicationNameapplication_name, deploymentGroupNamedeployment_group_name, revisionrevision_S3, descriptionTest deployment, ) deployment_id response[deploymentId] response client.get_deployment(deploymentIddeployment_id) assert response[deploymentInfo][revision][revisionType] S3 assert ( response[deploymentInfo][revision][s3Location] revision_S3[s3Location] ) assert response[deploymentInfo][status] Created assert createTime in response[deploymentInfo]3. 批量查询与列表操作mock_aws def test_batch_and_list(): client boto3.client(codedeploy, region_nameus-east-2) # 批量获取应用 client.create_application(applicationNamesample_app1, computePlatformLambda) client.create_application(applicationNamesample_app2, computePlatformServer) resp client.batch_get_applications(applicationNames[sample_app1, sample_app2]) assert len(resp[applicationsInfo]) 2 # 列出部署 service_role_arn arn:aws:iam::123456789012:role/CodeDeployDemoRole client.create_deployment_group( applicationNamesample_app1, deploymentGroupNamedg-1, serviceRoleArnservice_role_arn, ) client.create_deployment( applicationNamesample_app1, deploymentGroupNamedg-1, revision{revisionType: String, string: {content: c, sha256: s}}, ) resp client.list_deployments() assert len(resp[deployments]) 1 # 列出部署组 resp client.list_deployment_groups(applicationNamesample_app1) assert resp[deploymentGroups] [dg-1]list_deployments的参数配对校验同样值得在测试中固化对应 test_list_deployments_group_required、test_list_deployments_app_requiredmock_aws def test_list_deployments_validation(): client boto3.client(codedeploy, region_nameap-southeast-1) # 只给 applicationName必须同时给 deploymentGroupName with pytest.raises(ClientError) as exc: client.list_deployments(applicationNamemytestapp) assert exc.value.response[Error][Code] DeploymentGroupNameRequiredException # 只给 deploymentGroupName必须同时给 applicationName with pytest.raises(ClientError) as exc: client.list_deployments(deploymentGroupNamemygroupname) assert exc.value.response[Error][Code] ApplicationNameRequiredException4. 标签应用、部署组与部署的标签继承标签功能基于通用TaggingServicemodels.py#L247资源 ARN 格式为应用arn:aws:codedeploy:region:account:application:name部署组arn:aws:codedeploy:region:account:deploymentgroup:app/group部署arn:aws:codedeploy:region:account:deployment:deploymentId一个有意思的行为是部署会继承部署组的标签create_deployment时如果所属部署组带有 tags会把同样的 tags 打到部署 ARN 上models.py#L374-L379该行为由 test_deployment_inherits_tags 验证mock_aws def test_deployment_inherits_tags(): client boto3.client(codedeploy, region_nameus-west-2) app_name test-tag-inherit-app dg_name test-tag-inherit-group service_role_arn arn:aws:iam::123456789012:role/CodeDeployDemoRole client.create_application(applicationNameapp_name, computePlatformServer) client.create_deployment_group( applicationNameapp_name, deploymentGroupNamedg_name, serviceRoleArnservice_role_arn, tags[{Key: Environment, Value: Production}], ) response client.create_deployment( applicationNameapp_name, deploymentGroupNamedg_name, revision{revisionType: S3, s3Location: {bucket: b, key: k, bundleType: zip, version: 1, eTag: e}}, ) deployment_arn ( farn:aws:codedeploy:us-west-2:123456789012:deployment:{response[deploymentId]} ) response client.list_tags_for_resource(ResourceArndeployment_arn) assert response[Tags][0][Key] Environment应用与部署组自身的标签查询分别对应 test_application_tagging 与 test_deployment_group_tagging。使用前提与限制小结装饰器用法所有示例均基于from moto import mock_awsmock_aws装饰器context manager 或mock_aws.start()/stop()用法同理与 docs/docs/getting_started.rst 介绍的一致安装可用pip install moto[codedeploy]或moto[all]。能力边界只能模拟上文已实现的 16 个 API部署生命周期状态不会从Created自动流转startTime、completeTime保持None也不存在真实的 EC2/ASG 实例部署结果——如果你的测试断言部署最终变为Succeeded需要自行模拟。未实现 API 会直接失败调用清单中未勾选的 API如stop_deployment、delete_application、create_deployment_config时由于 dispatch 找不到对应 handler 会抛异常编写测试前先对照 docs/docs/services/codedeploy.rst 的清单。多区域隔离后端按account region存储BackendDict(CodeDeployBackend, codedeploy)不同 region 的客户端看到的资源互不干扰这与 test_list_deployments 等用例中显式指定region_name的写法一致。赞分享Mock测试【免费下载链接】motoA library that allows you to easily mock out tests based on AWS infrastructure.项目地址https://gitcode.com/gh_mirrors/mo/moto点击查看免费下载相关推荐moto 中的 CloudHSM V2 模拟:API 覆盖范围、后端实现与 mock 测试实战moto 中的 CloudHSM V2 模拟:API 覆盖范围、后端实现与 mock 测试实战 本文基于 moto 仓库的服务文档 cloudhsmv2.rstMock测试Moto CodeBuild 模拟实战在测试中 Mock AWS CodeBuild 项目与构建 APIMoto CodeBuild 模拟实战在测试中 Mock AWS CodeBuild 项目与构建 API 本篇技术指南围绕 moto 仓库中 CodeBuilMock测试boto3单元测试模拟框架moto vs localstackboto3单元测试模拟框架moto vs localstack 你是否还在为AWS服务单元测试发愁本地环境搭建复杂、测试成本高、依赖外部网络本文将对比两款后端云原生上一篇go2rtc项目中流媒体连接中断问题的分析与解决下一篇致命BUG修复实录Destiny 2 Solo Enabler布尔值解析异常深度排查与架构优化方案创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进