capo-codedeploy 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (472) hide show
  1. capo_codedeploy/__init__.py +70 -0
  2. capo_codedeploy/_async.py +25 -0
  3. capo_codedeploy/_auth/_identity.py +16 -0
  4. capo_codedeploy/_auth/_providers.py +886 -0
  5. capo_codedeploy/_auth/_signers.py +88 -0
  6. capo_codedeploy/_auth/_sigv4.py +434 -0
  7. capo_codedeploy/_auth/_zapros_handler.py +80 -0
  8. capo_codedeploy/_iter.py +113 -0
  9. capo_codedeploy/_operations/code_deploy_20141006/add_tags_to_on_premises_instances.py +153 -0
  10. capo_codedeploy/_operations/code_deploy_20141006/batch_get_application_revisions.py +173 -0
  11. capo_codedeploy/_operations/code_deploy_20141006/batch_get_applications.py +163 -0
  12. capo_codedeploy/_operations/code_deploy_20141006/batch_get_deployment_groups.py +178 -0
  13. capo_codedeploy/_operations/code_deploy_20141006/batch_get_deployment_instances.py +178 -0
  14. capo_codedeploy/_operations/code_deploy_20141006/batch_get_deployment_targets.py +188 -0
  15. capo_codedeploy/_operations/code_deploy_20141006/batch_get_deployments.py +158 -0
  16. capo_codedeploy/_operations/code_deploy_20141006/batch_get_on_premises_instances.py +158 -0
  17. capo_codedeploy/_operations/code_deploy_20141006/continue_deployment.py +155 -0
  18. capo_codedeploy/_operations/code_deploy_20141006/create_application.py +175 -0
  19. capo_codedeploy/_operations/code_deploy_20141006/create_deployment.py +278 -0
  20. capo_codedeploy/_operations/code_deploy_20141006/create_deployment_config.py +185 -0
  21. capo_codedeploy/_operations/code_deploy_20141006/create_deployment_group.py +320 -0
  22. capo_codedeploy/_operations/code_deploy_20141006/delete_application.py +129 -0
  23. capo_codedeploy/_operations/code_deploy_20141006/delete_deployment_config.py +136 -0
  24. capo_codedeploy/_operations/code_deploy_20141006/delete_deployment_group.py +167 -0
  25. capo_codedeploy/_operations/code_deploy_20141006/delete_git_hub_account_token.py +166 -0
  26. capo_codedeploy/_operations/code_deploy_20141006/delete_resources_by_external_id.py +137 -0
  27. capo_codedeploy/_operations/code_deploy_20141006/deregister_on_premises_instance.py +126 -0
  28. capo_codedeploy/_operations/code_deploy_20141006/get_application.py +157 -0
  29. capo_codedeploy/_operations/code_deploy_20141006/get_application_revision.py +173 -0
  30. capo_codedeploy/_operations/code_deploy_20141006/get_deployment.py +157 -0
  31. capo_codedeploy/_operations/code_deploy_20141006/get_deployment_config.py +162 -0
  32. capo_codedeploy/_operations/code_deploy_20141006/get_deployment_group.py +179 -0
  33. capo_codedeploy/_operations/code_deploy_20141006/get_deployment_instance.py +177 -0
  34. capo_codedeploy/_operations/code_deploy_20141006/get_deployment_target.py +182 -0
  35. capo_codedeploy/_operations/code_deploy_20141006/get_on_premises_instance.py +157 -0
  36. capo_codedeploy/_operations/code_deploy_20141006/list_application_revisions.py +195 -0
  37. capo_codedeploy/_operations/code_deploy_20141006/list_applications.py +149 -0
  38. capo_codedeploy/_operations/code_deploy_20141006/list_deployment_configs.py +147 -0
  39. capo_codedeploy/_operations/code_deploy_20141006/list_deployment_groups.py +162 -0
  40. capo_codedeploy/_operations/code_deploy_20141006/list_deployment_instances.py +198 -0
  41. capo_codedeploy/_operations/code_deploy_20141006/list_deployment_targets.py +188 -0
  42. capo_codedeploy/_operations/code_deploy_20141006/list_deployments.py +199 -0
  43. capo_codedeploy/_operations/code_deploy_20141006/list_git_hub_account_token_names.py +157 -0
  44. capo_codedeploy/_operations/code_deploy_20141006/list_on_premises_instances.py +159 -0
  45. capo_codedeploy/_operations/code_deploy_20141006/list_tags_for_resource.py +157 -0
  46. capo_codedeploy/_operations/code_deploy_20141006/put_lifecycle_event_hook_execution_status.py +177 -0
  47. capo_codedeploy/_operations/code_deploy_20141006/register_application_revision.py +147 -0
  48. capo_codedeploy/_operations/code_deploy_20141006/register_on_premises_instance.py +166 -0
  49. capo_codedeploy/_operations/code_deploy_20141006/remove_tags_from_on_premises_instances.py +153 -0
  50. capo_codedeploy/_operations/code_deploy_20141006/skip_wait_time_for_instance_termination.py +146 -0
  51. capo_codedeploy/_operations/code_deploy_20141006/stop_deployment.py +172 -0
  52. capo_codedeploy/_operations/code_deploy_20141006/tag_resource.py +174 -0
  53. capo_codedeploy/_operations/code_deploy_20141006/untag_resource.py +174 -0
  54. capo_codedeploy/_operations/code_deploy_20141006/update_application.py +134 -0
  55. capo_codedeploy/_operations/code_deploy_20141006/update_deployment_group.py +310 -0
  56. capo_codedeploy/_pagination.py +21 -0
  57. capo_codedeploy/_protocol/__init__.py +1 -0
  58. capo_codedeploy/_protocol/errors.py +93 -0
  59. capo_codedeploy/_protocol/eventstream.py +238 -0
  60. capo_codedeploy/_protocol/serialize.py +47 -0
  61. capo_codedeploy/_protocol/xml.py +33 -0
  62. capo_codedeploy/_rule_engine/__init__.py +0 -0
  63. capo_codedeploy/_rule_engine/_aws_partition.py +160 -0
  64. capo_codedeploy/_rule_engine/_endpoint_rule_set.py +138 -0
  65. capo_codedeploy/_rule_engine/_endpoint_runtime.py +389 -0
  66. capo_codedeploy/_services/_aws_config.py +160 -0
  67. capo_codedeploy/_services/_pipeline.py +198 -0
  68. capo_codedeploy/_services/async_code_deploy.py +3278 -0
  69. capo_codedeploy/_services/code_deploy.py +3229 -0
  70. capo_codedeploy/errors/__init__.py +335 -0
  71. capo_codedeploy/errors/_base.py +94 -0
  72. capo_codedeploy/errors/alarms_limit_exceeded_exception.py +52 -0
  73. capo_codedeploy/errors/application_already_exists_exception.py +54 -0
  74. capo_codedeploy/errors/application_does_not_exist_exception.py +54 -0
  75. capo_codedeploy/errors/application_limit_exceeded_exception.py +54 -0
  76. capo_codedeploy/errors/application_name_required_exception.py +54 -0
  77. capo_codedeploy/errors/arn_not_supported_exception.py +52 -0
  78. capo_codedeploy/errors/batch_limit_exceeded_exception.py +52 -0
  79. capo_codedeploy/errors/bucket_name_filter_required_exception.py +54 -0
  80. capo_codedeploy/errors/deployment_already_completed_exception.py +54 -0
  81. capo_codedeploy/errors/deployment_config_already_exists_exception.py +54 -0
  82. capo_codedeploy/errors/deployment_config_does_not_exist_exception.py +54 -0
  83. capo_codedeploy/errors/deployment_config_in_use_exception.py +54 -0
  84. capo_codedeploy/errors/deployment_config_limit_exceeded_exception.py +54 -0
  85. capo_codedeploy/errors/deployment_config_name_required_exception.py +54 -0
  86. capo_codedeploy/errors/deployment_does_not_exist_exception.py +54 -0
  87. capo_codedeploy/errors/deployment_group_already_exists_exception.py +54 -0
  88. capo_codedeploy/errors/deployment_group_does_not_exist_exception.py +54 -0
  89. capo_codedeploy/errors/deployment_group_limit_exceeded_exception.py +54 -0
  90. capo_codedeploy/errors/deployment_group_name_required_exception.py +54 -0
  91. capo_codedeploy/errors/deployment_id_required_exception.py +54 -0
  92. capo_codedeploy/errors/deployment_is_not_in_ready_state_exception.py +54 -0
  93. capo_codedeploy/errors/deployment_limit_exceeded_exception.py +54 -0
  94. capo_codedeploy/errors/deployment_not_started_exception.py +54 -0
  95. capo_codedeploy/errors/deployment_target_does_not_exist_exception.py +54 -0
  96. capo_codedeploy/errors/deployment_target_id_required_exception.py +54 -0
  97. capo_codedeploy/errors/deployment_target_list_size_exceeded_exception.py +56 -0
  98. capo_codedeploy/errors/description_too_long_exception.py +52 -0
  99. capo_codedeploy/errors/ecs_service_mapping_limit_exceeded_exception.py +54 -0
  100. capo_codedeploy/errors/git_hub_account_token_does_not_exist_exception.py +54 -0
  101. capo_codedeploy/errors/git_hub_account_token_name_required_exception.py +54 -0
  102. capo_codedeploy/errors/iam_arn_required_exception.py +52 -0
  103. capo_codedeploy/errors/iam_session_arn_already_registered_exception.py +54 -0
  104. capo_codedeploy/errors/iam_user_arn_already_registered_exception.py +54 -0
  105. capo_codedeploy/errors/iam_user_arn_required_exception.py +52 -0
  106. capo_codedeploy/errors/instance_does_not_exist_exception.py +54 -0
  107. capo_codedeploy/errors/instance_id_required_exception.py +52 -0
  108. capo_codedeploy/errors/instance_limit_exceeded_exception.py +54 -0
  109. capo_codedeploy/errors/instance_name_already_registered_exception.py +54 -0
  110. capo_codedeploy/errors/instance_name_required_exception.py +54 -0
  111. capo_codedeploy/errors/instance_not_registered_exception.py +54 -0
  112. capo_codedeploy/errors/invalid_alarm_config_exception.py +52 -0
  113. capo_codedeploy/errors/invalid_application_name_exception.py +54 -0
  114. capo_codedeploy/errors/invalid_arn_exception.py +52 -0
  115. capo_codedeploy/errors/invalid_auto_rollback_config_exception.py +54 -0
  116. capo_codedeploy/errors/invalid_auto_scaling_group_exception.py +54 -0
  117. capo_codedeploy/errors/invalid_blue_green_deployment_configuration_exception.py +60 -0
  118. capo_codedeploy/errors/invalid_bucket_name_filter_exception.py +54 -0
  119. capo_codedeploy/errors/invalid_compute_platform_exception.py +54 -0
  120. capo_codedeploy/errors/invalid_deployed_state_filter_exception.py +54 -0
  121. capo_codedeploy/errors/invalid_deployment_config_name_exception.py +54 -0
  122. capo_codedeploy/errors/invalid_deployment_group_name_exception.py +54 -0
  123. capo_codedeploy/errors/invalid_deployment_id_exception.py +52 -0
  124. capo_codedeploy/errors/invalid_deployment_instance_type_exception.py +54 -0
  125. capo_codedeploy/errors/invalid_deployment_status_exception.py +54 -0
  126. capo_codedeploy/errors/invalid_deployment_style_exception.py +54 -0
  127. capo_codedeploy/errors/invalid_deployment_target_id_exception.py +54 -0
  128. capo_codedeploy/errors/invalid_deployment_wait_type_exception.py +54 -0
  129. capo_codedeploy/errors/invalid_ec2_tag_combination_exception.py +54 -0
  130. capo_codedeploy/errors/invalid_ec2_tag_exception.py +52 -0
  131. capo_codedeploy/errors/invalid_ecs_service_exception.py +52 -0
  132. capo_codedeploy/errors/invalid_external_id_exception.py +52 -0
  133. capo_codedeploy/errors/invalid_file_exists_behavior_exception.py +54 -0
  134. capo_codedeploy/errors/invalid_git_hub_account_token_exception.py +54 -0
  135. capo_codedeploy/errors/invalid_git_hub_account_token_name_exception.py +54 -0
  136. capo_codedeploy/errors/invalid_iam_session_arn_exception.py +54 -0
  137. capo_codedeploy/errors/invalid_iam_user_arn_exception.py +52 -0
  138. capo_codedeploy/errors/invalid_ignore_application_stop_failures_value_exception.py +60 -0
  139. capo_codedeploy/errors/invalid_input_exception.py +52 -0
  140. capo_codedeploy/errors/invalid_instance_name_exception.py +52 -0
  141. capo_codedeploy/errors/invalid_instance_status_exception.py +54 -0
  142. capo_codedeploy/errors/invalid_instance_type_exception.py +52 -0
  143. capo_codedeploy/errors/invalid_key_prefix_filter_exception.py +54 -0
  144. capo_codedeploy/errors/invalid_lifecycle_event_hook_execution_id_exception.py +60 -0
  145. capo_codedeploy/errors/invalid_lifecycle_event_hook_execution_status_exception.py +60 -0
  146. capo_codedeploy/errors/invalid_load_balancer_info_exception.py +54 -0
  147. capo_codedeploy/errors/invalid_minimum_healthy_host_value_exception.py +54 -0
  148. capo_codedeploy/errors/invalid_next_token_exception.py +52 -0
  149. capo_codedeploy/errors/invalid_on_premises_tag_combination_exception.py +56 -0
  150. capo_codedeploy/errors/invalid_operation_exception.py +52 -0
  151. capo_codedeploy/errors/invalid_registration_status_exception.py +54 -0
  152. capo_codedeploy/errors/invalid_revision_exception.py +52 -0
  153. capo_codedeploy/errors/invalid_role_exception.py +52 -0
  154. capo_codedeploy/errors/invalid_sort_by_exception.py +52 -0
  155. capo_codedeploy/errors/invalid_sort_order_exception.py +52 -0
  156. capo_codedeploy/errors/invalid_tag_exception.py +52 -0
  157. capo_codedeploy/errors/invalid_tag_filter_exception.py +52 -0
  158. capo_codedeploy/errors/invalid_tags_to_add_exception.py +52 -0
  159. capo_codedeploy/errors/invalid_target_filter_name_exception.py +54 -0
  160. capo_codedeploy/errors/invalid_target_group_pair_exception.py +54 -0
  161. capo_codedeploy/errors/invalid_target_instances_exception.py +54 -0
  162. capo_codedeploy/errors/invalid_time_range_exception.py +52 -0
  163. capo_codedeploy/errors/invalid_traffic_routing_configuration_exception.py +58 -0
  164. capo_codedeploy/errors/invalid_trigger_config_exception.py +54 -0
  165. capo_codedeploy/errors/invalid_update_outdated_instances_only_value_exception.py +60 -0
  166. capo_codedeploy/errors/invalid_zonal_deployment_configuration_exception.py +60 -0
  167. capo_codedeploy/errors/lifecycle_event_already_completed_exception.py +54 -0
  168. capo_codedeploy/errors/lifecycle_hook_limit_exceeded_exception.py +54 -0
  169. capo_codedeploy/errors/multiple_iam_arns_provided_exception.py +54 -0
  170. capo_codedeploy/errors/operation_not_supported_exception.py +54 -0
  171. capo_codedeploy/errors/resource_arn_required_exception.py +52 -0
  172. capo_codedeploy/errors/resource_validation_exception.py +52 -0
  173. capo_codedeploy/errors/revision_does_not_exist_exception.py +54 -0
  174. capo_codedeploy/errors/revision_required_exception.py +52 -0
  175. capo_codedeploy/errors/role_required_exception.py +52 -0
  176. capo_codedeploy/errors/tag_limit_exceeded_exception.py +52 -0
  177. capo_codedeploy/errors/tag_required_exception.py +52 -0
  178. capo_codedeploy/errors/tag_set_list_limit_exceeded_exception.py +54 -0
  179. capo_codedeploy/errors/throttling_exception.py +52 -0
  180. capo_codedeploy/errors/trigger_targets_limit_exceeded_exception.py +54 -0
  181. capo_codedeploy/errors/unsupported_action_for_deployment_type_exception.py +58 -0
  182. capo_codedeploy/py.typed +0 -0
  183. capo_codedeploy/types/_prelude/blob.py +12 -0
  184. capo_codedeploy/types/_prelude/timestamp.py +17 -0
  185. capo_codedeploy/types/add_tags_to_on_premises_instances_input.py +59 -0
  186. capo_codedeploy/types/additional_deployment_status_info.py +5 -0
  187. capo_codedeploy/types/alarm.py +28 -0
  188. capo_codedeploy/types/alarm_configuration.py +51 -0
  189. capo_codedeploy/types/alarm_list.py +29 -0
  190. capo_codedeploy/types/alarm_name.py +5 -0
  191. capo_codedeploy/types/app_spec_content.py +35 -0
  192. capo_codedeploy/types/application_id.py +5 -0
  193. capo_codedeploy/types/application_info.py +90 -0
  194. capo_codedeploy/types/application_name.py +5 -0
  195. capo_codedeploy/types/application_revision_sort_by.py +18 -0
  196. capo_codedeploy/types/applications_info_list.py +33 -0
  197. capo_codedeploy/types/applications_list.py +19 -0
  198. capo_codedeploy/types/arn.py +5 -0
  199. capo_codedeploy/types/auto_rollback_configuration.py +50 -0
  200. capo_codedeploy/types/auto_rollback_event.py +18 -0
  201. capo_codedeploy/types/auto_rollback_events_list.py +35 -0
  202. capo_codedeploy/types/auto_scaling_group.py +47 -0
  203. capo_codedeploy/types/auto_scaling_group_hook.py +5 -0
  204. capo_codedeploy/types/auto_scaling_group_list.py +35 -0
  205. capo_codedeploy/types/auto_scaling_group_name.py +5 -0
  206. capo_codedeploy/types/auto_scaling_group_name_list.py +19 -0
  207. capo_codedeploy/types/batch_get_application_revisions_input.py +55 -0
  208. capo_codedeploy/types/batch_get_application_revisions_output.py +56 -0
  209. capo_codedeploy/types/batch_get_applications_input.py +45 -0
  210. capo_codedeploy/types/batch_get_applications_output.py +42 -0
  211. capo_codedeploy/types/batch_get_deployment_groups_input.py +57 -0
  212. capo_codedeploy/types/batch_get_deployment_groups_output.py +49 -0
  213. capo_codedeploy/types/batch_get_deployment_instances_input.py +53 -0
  214. capo_codedeploy/types/batch_get_deployment_instances_output.py +49 -0
  215. capo_codedeploy/types/batch_get_deployment_targets_input.py +51 -0
  216. capo_codedeploy/types/batch_get_deployment_targets_output.py +42 -0
  217. capo_codedeploy/types/batch_get_deployments_input.py +43 -0
  218. capo_codedeploy/types/batch_get_deployments_output.py +42 -0
  219. capo_codedeploy/types/batch_get_on_premises_instances_input.py +45 -0
  220. capo_codedeploy/types/batch_get_on_premises_instances_output.py +42 -0
  221. capo_codedeploy/types/blue_green_deployment_configuration.py +84 -0
  222. capo_codedeploy/types/blue_instance_termination_option.py +46 -0
  223. capo_codedeploy/types/boolean.py +5 -0
  224. capo_codedeploy/types/bundle_type.py +20 -0
  225. capo_codedeploy/types/cloud_formation_resource_type.py +5 -0
  226. capo_codedeploy/types/cloud_formation_target.py +112 -0
  227. capo_codedeploy/types/commit_id.py +5 -0
  228. capo_codedeploy/types/compute_platform.py +18 -0
  229. capo_codedeploy/types/continue_deployment_input.py +49 -0
  230. capo_codedeploy/types/create_application_input.py +67 -0
  231. capo_codedeploy/types/create_application_output.py +28 -0
  232. capo_codedeploy/types/create_deployment_config_input.py +115 -0
  233. capo_codedeploy/types/create_deployment_config_output.py +30 -0
  234. capo_codedeploy/types/create_deployment_group_input.py +353 -0
  235. capo_codedeploy/types/create_deployment_group_output.py +30 -0
  236. capo_codedeploy/types/create_deployment_input.py +178 -0
  237. capo_codedeploy/types/create_deployment_output.py +28 -0
  238. capo_codedeploy/types/delete_application_input.py +31 -0
  239. capo_codedeploy/types/delete_deployment_config_input.py +35 -0
  240. capo_codedeploy/types/delete_deployment_group_input.py +45 -0
  241. capo_codedeploy/types/delete_deployment_group_output.py +42 -0
  242. capo_codedeploy/types/delete_git_hub_account_token_input.py +30 -0
  243. capo_codedeploy/types/delete_git_hub_account_token_output.py +30 -0
  244. capo_codedeploy/types/delete_resources_by_external_id_input.py +28 -0
  245. capo_codedeploy/types/delete_resources_by_external_id_output.py +18 -0
  246. capo_codedeploy/types/deployment_config_id.py +5 -0
  247. capo_codedeploy/types/deployment_config_info.py +134 -0
  248. capo_codedeploy/types/deployment_config_name.py +5 -0
  249. capo_codedeploy/types/deployment_configs_list.py +19 -0
  250. capo_codedeploy/types/deployment_creator.py +23 -0
  251. capo_codedeploy/types/deployment_group_id.py +5 -0
  252. capo_codedeploy/types/deployment_group_info.py +420 -0
  253. capo_codedeploy/types/deployment_group_info_list.py +35 -0
  254. capo_codedeploy/types/deployment_group_name.py +5 -0
  255. capo_codedeploy/types/deployment_groups_list.py +19 -0
  256. capo_codedeploy/types/deployment_id.py +5 -0
  257. capo_codedeploy/types/deployment_info.py +499 -0
  258. capo_codedeploy/types/deployment_option.py +17 -0
  259. capo_codedeploy/types/deployment_overview.py +64 -0
  260. capo_codedeploy/types/deployment_ready_action.py +17 -0
  261. capo_codedeploy/types/deployment_ready_option.py +50 -0
  262. capo_codedeploy/types/deployment_status.py +23 -0
  263. capo_codedeploy/types/deployment_status_list.py +33 -0
  264. capo_codedeploy/types/deployment_status_message_list.py +19 -0
  265. capo_codedeploy/types/deployment_style.py +61 -0
  266. capo_codedeploy/types/deployment_target.py +115 -0
  267. capo_codedeploy/types/deployment_target_list.py +33 -0
  268. capo_codedeploy/types/deployment_target_type.py +19 -0
  269. capo_codedeploy/types/deployment_type.py +17 -0
  270. capo_codedeploy/types/deployment_wait_type.py +17 -0
  271. capo_codedeploy/types/deployments_info_list.py +31 -0
  272. capo_codedeploy/types/deployments_list.py +17 -0
  273. capo_codedeploy/types/deregister_on_premises_instance_input.py +33 -0
  274. capo_codedeploy/types/description.py +5 -0
  275. capo_codedeploy/types/diagnostics.py +63 -0
  276. capo_codedeploy/types/duration.py +5 -0
  277. capo_codedeploy/types/e_tag.py +5 -0
  278. capo_codedeploy/types/ec2_tag_filter.py +52 -0
  279. capo_codedeploy/types/ec2_tag_filter_list.py +29 -0
  280. capo_codedeploy/types/ec2_tag_filter_type.py +18 -0
  281. capo_codedeploy/types/ec2_tag_set.py +42 -0
  282. capo_codedeploy/types/ec2_tag_set_list.py +35 -0
  283. capo_codedeploy/types/ecs_cluster_name.py +5 -0
  284. capo_codedeploy/types/ecs_service.py +35 -0
  285. capo_codedeploy/types/ecs_service_list.py +29 -0
  286. capo_codedeploy/types/ecs_service_name.py +5 -0
  287. capo_codedeploy/types/ecs_target.py +114 -0
  288. capo_codedeploy/types/ecs_task_set.py +111 -0
  289. capo_codedeploy/types/ecs_task_set_count.py +5 -0
  290. capo_codedeploy/types/ecs_task_set_identifier.py +5 -0
  291. capo_codedeploy/types/ecs_task_set_list.py +29 -0
  292. capo_codedeploy/types/ecs_task_set_status.py +5 -0
  293. capo_codedeploy/types/elb_info.py +28 -0
  294. capo_codedeploy/types/elb_info_list.py +29 -0
  295. capo_codedeploy/types/elb_name.py +5 -0
  296. capo_codedeploy/types/error_code.py +49 -0
  297. capo_codedeploy/types/error_information.py +43 -0
  298. capo_codedeploy/types/error_message.py +5 -0
  299. capo_codedeploy/types/external_id.py +5 -0
  300. capo_codedeploy/types/file_exists_behavior.py +18 -0
  301. capo_codedeploy/types/filter_value.py +5 -0
  302. capo_codedeploy/types/filter_value_list.py +17 -0
  303. capo_codedeploy/types/generic_revision_info.py +96 -0
  304. capo_codedeploy/types/get_application_input.py +31 -0
  305. capo_codedeploy/types/get_application_output.py +40 -0
  306. capo_codedeploy/types/get_application_revision_input.py +51 -0
  307. capo_codedeploy/types/get_application_revision_output.py +70 -0
  308. capo_codedeploy/types/get_deployment_config_input.py +35 -0
  309. capo_codedeploy/types/get_deployment_config_output.py +42 -0
  310. capo_codedeploy/types/get_deployment_group_input.py +43 -0
  311. capo_codedeploy/types/get_deployment_group_output.py +42 -0
  312. capo_codedeploy/types/get_deployment_input.py +31 -0
  313. capo_codedeploy/types/get_deployment_instance_input.py +39 -0
  314. capo_codedeploy/types/get_deployment_instance_output.py +42 -0
  315. capo_codedeploy/types/get_deployment_output.py +40 -0
  316. capo_codedeploy/types/get_deployment_target_input.py +39 -0
  317. capo_codedeploy/types/get_deployment_target_output.py +42 -0
  318. capo_codedeploy/types/get_on_premises_instance_input.py +31 -0
  319. capo_codedeploy/types/get_on_premises_instance_output.py +40 -0
  320. capo_codedeploy/types/git_hub_account_token_name.py +5 -0
  321. capo_codedeploy/types/git_hub_account_token_name_list.py +19 -0
  322. capo_codedeploy/types/git_hub_location.py +35 -0
  323. capo_codedeploy/types/green_fleet_provisioning_action.py +17 -0
  324. capo_codedeploy/types/green_fleet_provisioning_option.py +42 -0
  325. capo_codedeploy/types/iam_session_arn.py +5 -0
  326. capo_codedeploy/types/iam_user_arn.py +5 -0
  327. capo_codedeploy/types/instance_action.py +17 -0
  328. capo_codedeploy/types/instance_arn.py +5 -0
  329. capo_codedeploy/types/instance_count.py +5 -0
  330. capo_codedeploy/types/instance_id.py +5 -0
  331. capo_codedeploy/types/instance_info.py +95 -0
  332. capo_codedeploy/types/instance_info_list.py +29 -0
  333. capo_codedeploy/types/instance_name.py +5 -0
  334. capo_codedeploy/types/instance_name_list.py +17 -0
  335. capo_codedeploy/types/instance_status.py +22 -0
  336. capo_codedeploy/types/instance_status_list.py +31 -0
  337. capo_codedeploy/types/instance_summary.py +107 -0
  338. capo_codedeploy/types/instance_summary_list.py +33 -0
  339. capo_codedeploy/types/instance_target.py +112 -0
  340. capo_codedeploy/types/instance_type.py +17 -0
  341. capo_codedeploy/types/instance_type_list.py +29 -0
  342. capo_codedeploy/types/instances_list.py +17 -0
  343. capo_codedeploy/types/key.py +5 -0
  344. capo_codedeploy/types/lambda_function_alias.py +5 -0
  345. capo_codedeploy/types/lambda_function_info.py +69 -0
  346. capo_codedeploy/types/lambda_function_name.py +5 -0
  347. capo_codedeploy/types/lambda_target.py +114 -0
  348. capo_codedeploy/types/last_deployment_info.py +74 -0
  349. capo_codedeploy/types/lifecycle_error_code.py +21 -0
  350. capo_codedeploy/types/lifecycle_event.py +95 -0
  351. capo_codedeploy/types/lifecycle_event_hook_execution_id.py +5 -0
  352. capo_codedeploy/types/lifecycle_event_list.py +31 -0
  353. capo_codedeploy/types/lifecycle_event_name.py +5 -0
  354. capo_codedeploy/types/lifecycle_event_status.py +21 -0
  355. capo_codedeploy/types/lifecycle_message.py +5 -0
  356. capo_codedeploy/types/list_application_revisions_input.py +111 -0
  357. capo_codedeploy/types/list_application_revisions_output.py +49 -0
  358. capo_codedeploy/types/list_applications_input.py +28 -0
  359. capo_codedeploy/types/list_applications_output.py +49 -0
  360. capo_codedeploy/types/list_deployment_configs_input.py +28 -0
  361. capo_codedeploy/types/list_deployment_configs_output.py +49 -0
  362. capo_codedeploy/types/list_deployment_groups_input.py +40 -0
  363. capo_codedeploy/types/list_deployment_groups_output.py +58 -0
  364. capo_codedeploy/types/list_deployment_instances_input.py +82 -0
  365. capo_codedeploy/types/list_deployment_instances_output.py +47 -0
  366. capo_codedeploy/types/list_deployment_targets_input.py +57 -0
  367. capo_codedeploy/types/list_deployment_targets_output.py +45 -0
  368. capo_codedeploy/types/list_deployments_input.py +93 -0
  369. capo_codedeploy/types/list_deployments_output.py +47 -0
  370. capo_codedeploy/types/list_git_hub_account_token_names_input.py +28 -0
  371. capo_codedeploy/types/list_git_hub_account_token_names_output.py +49 -0
  372. capo_codedeploy/types/list_on_premises_instances_input.py +68 -0
  373. capo_codedeploy/types/list_on_premises_instances_output.py +49 -0
  374. capo_codedeploy/types/list_state_filter_action.py +18 -0
  375. capo_codedeploy/types/list_tags_for_resource_input.py +38 -0
  376. capo_codedeploy/types/list_tags_for_resource_output.py +43 -0
  377. capo_codedeploy/types/listener_arn.py +5 -0
  378. capo_codedeploy/types/listener_arn_list.py +17 -0
  379. capo_codedeploy/types/load_balancer_info.py +80 -0
  380. capo_codedeploy/types/log_tail.py +5 -0
  381. capo_codedeploy/types/message.py +5 -0
  382. capo_codedeploy/types/minimum_healthy_hosts.py +50 -0
  383. capo_codedeploy/types/minimum_healthy_hosts_per_zone.py +50 -0
  384. capo_codedeploy/types/minimum_healthy_hosts_per_zone_type.py +17 -0
  385. capo_codedeploy/types/minimum_healthy_hosts_per_zone_value.py +5 -0
  386. capo_codedeploy/types/minimum_healthy_hosts_type.py +17 -0
  387. capo_codedeploy/types/minimum_healthy_hosts_value.py +5 -0
  388. capo_codedeploy/types/next_token.py +5 -0
  389. capo_codedeploy/types/nullable_boolean.py +5 -0
  390. capo_codedeploy/types/on_premises_tag_set.py +42 -0
  391. capo_codedeploy/types/on_premises_tag_set_list.py +31 -0
  392. capo_codedeploy/types/outdated_instances_strategy.py +17 -0
  393. capo_codedeploy/types/percentage.py +5 -0
  394. capo_codedeploy/types/put_lifecycle_event_hook_execution_status_input.py +60 -0
  395. capo_codedeploy/types/put_lifecycle_event_hook_execution_status_output.py +32 -0
  396. capo_codedeploy/types/raw_string.py +35 -0
  397. capo_codedeploy/types/raw_string_content.py +5 -0
  398. capo_codedeploy/types/raw_string_sha256.py +5 -0
  399. capo_codedeploy/types/register_application_revision_input.py +58 -0
  400. capo_codedeploy/types/register_on_premises_instance_input.py +47 -0
  401. capo_codedeploy/types/registration_status.py +17 -0
  402. capo_codedeploy/types/related_deployments.py +55 -0
  403. capo_codedeploy/types/remove_tags_from_on_premises_instances_input.py +61 -0
  404. capo_codedeploy/types/repository.py +5 -0
  405. capo_codedeploy/types/revision_info.py +63 -0
  406. capo_codedeploy/types/revision_info_list.py +29 -0
  407. capo_codedeploy/types/revision_location.py +114 -0
  408. capo_codedeploy/types/revision_location_list.py +33 -0
  409. capo_codedeploy/types/revision_location_type.py +19 -0
  410. capo_codedeploy/types/role.py +5 -0
  411. capo_codedeploy/types/rollback_info.py +49 -0
  412. capo_codedeploy/types/s3_bucket.py +5 -0
  413. capo_codedeploy/types/s3_key.py +5 -0
  414. capo_codedeploy/types/s3_location.py +64 -0
  415. capo_codedeploy/types/script_name.py +5 -0
  416. capo_codedeploy/types/skip_wait_time_for_instance_termination_input.py +28 -0
  417. capo_codedeploy/types/sort_order.py +17 -0
  418. capo_codedeploy/types/stop_deployment_input.py +40 -0
  419. capo_codedeploy/types/stop_deployment_output.py +43 -0
  420. capo_codedeploy/types/stop_status.py +17 -0
  421. capo_codedeploy/types/tag.py +35 -0
  422. capo_codedeploy/types/tag_filter.py +50 -0
  423. capo_codedeploy/types/tag_filter_list.py +29 -0
  424. capo_codedeploy/types/tag_filter_type.py +18 -0
  425. capo_codedeploy/types/tag_key_list.py +17 -0
  426. capo_codedeploy/types/tag_list.py +29 -0
  427. capo_codedeploy/types/tag_resource_input.py +45 -0
  428. capo_codedeploy/types/tag_resource_output.py +18 -0
  429. capo_codedeploy/types/target_arn.py +5 -0
  430. capo_codedeploy/types/target_filter_name.py +17 -0
  431. capo_codedeploy/types/target_filters.py +40 -0
  432. capo_codedeploy/types/target_group_info.py +28 -0
  433. capo_codedeploy/types/target_group_info_list.py +33 -0
  434. capo_codedeploy/types/target_group_name.py +5 -0
  435. capo_codedeploy/types/target_group_pair_info.py +79 -0
  436. capo_codedeploy/types/target_group_pair_info_list.py +35 -0
  437. capo_codedeploy/types/target_id.py +5 -0
  438. capo_codedeploy/types/target_id_list.py +17 -0
  439. capo_codedeploy/types/target_instances.py +78 -0
  440. capo_codedeploy/types/target_label.py +17 -0
  441. capo_codedeploy/types/target_status.py +22 -0
  442. capo_codedeploy/types/time.py +20 -0
  443. capo_codedeploy/types/time_based_canary.py +37 -0
  444. capo_codedeploy/types/time_based_linear.py +37 -0
  445. capo_codedeploy/types/time_range.py +50 -0
  446. capo_codedeploy/types/timestamp.py +20 -0
  447. capo_codedeploy/types/traffic_route.py +42 -0
  448. capo_codedeploy/types/traffic_routing_config.py +80 -0
  449. capo_codedeploy/types/traffic_routing_type.py +18 -0
  450. capo_codedeploy/types/traffic_weight.py +5 -0
  451. capo_codedeploy/types/trigger_config.py +58 -0
  452. capo_codedeploy/types/trigger_config_list.py +31 -0
  453. capo_codedeploy/types/trigger_event_type.py +25 -0
  454. capo_codedeploy/types/trigger_event_type_list.py +35 -0
  455. capo_codedeploy/types/trigger_name.py +5 -0
  456. capo_codedeploy/types/trigger_target_arn.py +5 -0
  457. capo_codedeploy/types/untag_resource_input.py +47 -0
  458. capo_codedeploy/types/untag_resource_output.py +18 -0
  459. capo_codedeploy/types/update_application_input.py +38 -0
  460. capo_codedeploy/types/update_deployment_group_input.py +343 -0
  461. capo_codedeploy/types/update_deployment_group_output.py +42 -0
  462. capo_codedeploy/types/value.py +5 -0
  463. capo_codedeploy/types/version.py +5 -0
  464. capo_codedeploy/types/version_id.py +5 -0
  465. capo_codedeploy/types/wait_time_in_mins.py +5 -0
  466. capo_codedeploy/types/wait_time_in_seconds.py +5 -0
  467. capo_codedeploy/types/zonal_config.py +63 -0
  468. capo_codedeploy-0.1.0.dist-info/METADATA +102 -0
  469. capo_codedeploy-0.1.0.dist-info/RECORD +472 -0
  470. capo_codedeploy-0.1.0.dist-info/WHEEL +5 -0
  471. capo_codedeploy-0.1.0.dist-info/licenses/LICENSE +21 -0
  472. capo_codedeploy-0.1.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,886 @@
1
+ from __future__ import annotations
2
+
3
+ import configparser
4
+ import hashlib
5
+ import json
6
+ import os
7
+ import time
8
+ from abc import abstractmethod
9
+ from dataclasses import dataclass
10
+ from datetime import datetime, timedelta, timezone
11
+ from pathlib import Path
12
+ from typing import TYPE_CHECKING, Generic, TypeVar, cast
13
+
14
+ from zapros import AsyncClient, Client, ZaprosError
15
+
16
+ from capo_codedeploy._auth._identity import (
17
+ Credentials,
18
+ Identity,
19
+ )
20
+ from capo_codedeploy._services._aws_config import (
21
+ _load_profile,
22
+ active_profile,
23
+ config_file,
24
+ )
25
+
26
+ # The SSO, assume-role and web-identity providers call other AWS services, so
27
+ # they need the `sso` extra: `pip install capo-codedeploy[sso]`. Without it the imports
28
+ # below stay `None` and only those providers fail — see `require_dependency`.
29
+ if TYPE_CHECKING:
30
+ import capo_sso
31
+ import capo_sso.errors
32
+ import capo_sso.types.get_role_credentials_response
33
+ import capo_sso_oidc
34
+ import capo_sso_oidc.errors
35
+ import capo_sso_oidc.types.create_token_response
36
+ import capo_sts
37
+ import capo_sts.types.credentials
38
+ else:
39
+ try:
40
+ import capo_sso
41
+ import capo_sso.errors
42
+ except ImportError:
43
+ capo_sso = None
44
+ try:
45
+ import capo_sso_oidc
46
+ import capo_sso_oidc.errors
47
+ except ImportError:
48
+ capo_sso_oidc = None
49
+ try:
50
+ import capo_sts
51
+ except ImportError:
52
+ capo_sts = None
53
+
54
+ # refresh an SSO token this long before it actually expires
55
+ SSO_TOKEN_REFRESH_WINDOW = timedelta(minutes=5)
56
+
57
+
58
+ class IdentityNotFound(Exception):
59
+ """Raised when a provider cannot resolve an identity. Chain continues."""
60
+
61
+
62
+ class MissingDependencyError(Exception):
63
+ """Raised when a provider needs the `sso` extra but it is not installed."""
64
+
65
+
66
+ class SSOError(Exception):
67
+ """Raised when SSO is configured but unusable. Chain stops."""
68
+
69
+
70
+ class AssumeRoleError(Exception):
71
+ """Raised when a `role_arn` profile is configured but unusable. Chain stops."""
72
+
73
+
74
+ def require_dependency(module: object | None, package: str) -> None:
75
+ if module is None:
76
+ raise MissingDependencyError(
77
+ f"{package} is required for this credentials provider but is not "
78
+ "installed; reinstall with the sso feature enabled: capo-codedeploy[sso]"
79
+ )
80
+
81
+
82
+ IdentityT = TypeVar("IdentityT", bound="Identity")
83
+
84
+
85
+ class IdentityProvider(Generic[IdentityT]):
86
+ @abstractmethod
87
+ def resolve_identity(self) -> IdentityT:
88
+ raise NotImplementedError
89
+
90
+ async def aresolve_identity(self) -> IdentityT:
91
+ # default: no network I/O, reuse the sync resolution
92
+ return self.resolve_identity()
93
+
94
+
95
+ class ChainedProvider(IdentityProvider[IdentityT]):
96
+ """Try each provider in order; first non-`IdentityNotFound` wins."""
97
+
98
+ def __init__(self, *providers: IdentityProvider[IdentityT]) -> None:
99
+ if not providers:
100
+ raise ValueError("ChainedProvider requires at least one provider")
101
+ self._providers = providers
102
+
103
+ def resolve_identity(self) -> IdentityT:
104
+ errors: list[str] = []
105
+ for p in self._providers:
106
+ try:
107
+ return p.resolve_identity()
108
+ except IdentityNotFound as e:
109
+ errors.append(f"{type(p).__name__}: {e}")
110
+ raise IdentityNotFound("no provider succeeded: " + "; ".join(errors))
111
+
112
+ async def aresolve_identity(self) -> IdentityT:
113
+ errors: list[str] = []
114
+ for p in self._providers:
115
+ try:
116
+ return await p.aresolve_identity()
117
+ except IdentityNotFound as e:
118
+ errors.append(f"{type(p).__name__}: {e}")
119
+ raise IdentityNotFound("no provider succeeded: " + "; ".join(errors))
120
+
121
+
122
+ class CachedProvider(IdentityProvider[IdentityT]):
123
+ """Cache an identity until its `expiration` (minus skew) elapses."""
124
+
125
+ _SKEW_SECONDS = 60
126
+
127
+ def __init__(self, inner: IdentityProvider[IdentityT]) -> None:
128
+ self._inner = inner
129
+ self._cached: IdentityT | None = None
130
+
131
+ def resolve_identity(self) -> IdentityT:
132
+ if self._cached is not None and not self._expired(self._cached):
133
+ return self._cached
134
+ self._cached = self._inner.resolve_identity()
135
+ return self._cached
136
+
137
+ async def aresolve_identity(self) -> IdentityT:
138
+ if self._cached is not None and not self._expired(self._cached):
139
+ return self._cached
140
+ self._cached = await self._inner.aresolve_identity()
141
+ return self._cached
142
+
143
+ @classmethod
144
+ def _expired(cls, ident: Identity) -> bool:
145
+ exp = ident.get("expiration")
146
+ if exp is None:
147
+ return False
148
+ return (exp - datetime.now(timezone.utc)).total_seconds() <= cls._SKEW_SECONDS
149
+
150
+
151
+ class CredentialsProvider(IdentityProvider[Credentials]):
152
+ """Base class for providers that resolve AWS `Credentials`."""
153
+
154
+ @abstractmethod
155
+ def resolve_identity(self) -> Credentials:
156
+ raise NotImplementedError
157
+
158
+
159
+ class StaticAwsCredentialsProvider(CredentialsProvider):
160
+ def __init__(self, credentials: Credentials) -> None:
161
+ self._credentials = credentials
162
+
163
+ def resolve_identity(self) -> Credentials:
164
+ return self._credentials
165
+
166
+
167
+ class EnvCredentialsProvider(CredentialsProvider):
168
+ """Read AWS_ACCESS_KEY_ID / AWS_SECRET_ACCESS_KEY / AWS_SESSION_TOKEN."""
169
+
170
+ def resolve_identity(self) -> Credentials:
171
+ ak = os.environ.get("AWS_ACCESS_KEY_ID")
172
+ sk = os.environ.get("AWS_SECRET_ACCESS_KEY")
173
+ if not ak or not sk:
174
+ raise IdentityNotFound("AWS_ACCESS_KEY_ID / AWS_SECRET_ACCESS_KEY unset")
175
+ out: Credentials = {"access_key": ak, "secret_key": sk}
176
+ token = os.environ.get("AWS_SESSION_TOKEN")
177
+ if token:
178
+ out["session_token"] = token
179
+ return out
180
+
181
+
182
+ class ProfileCredentialsProvider(CredentialsProvider):
183
+ """Read ~/.aws/credentials and ~/.aws/config for the active profile."""
184
+
185
+ def __init__(
186
+ self, credentials_file: Path | None = None, profile: str | None = None
187
+ ) -> None:
188
+ self._profile = profile or active_profile()
189
+ self._cred_file = credentials_file or Path(
190
+ os.environ.get("AWS_SHARED_CREDENTIALS_FILE")
191
+ or Path.home() / ".aws" / "credentials"
192
+ )
193
+
194
+ def resolve_identity(self) -> Credentials:
195
+ section = self._load_section()
196
+ ak = section.get("aws_access_key_id")
197
+ sk = section.get("aws_secret_access_key")
198
+ if not ak or not sk:
199
+ raise IdentityNotFound(
200
+ f"profile {self._profile!r}: missing aws_access_key_id/aws_secret_access_key"
201
+ )
202
+ out: Credentials = {"access_key": ak, "secret_key": sk}
203
+ token = section.get("aws_session_token")
204
+ if token:
205
+ out["session_token"] = token
206
+ return out
207
+
208
+ def _load_section(self) -> dict[str, str]:
209
+ # config-file profile reuses the loader from _services/_aws_config
210
+ merged, _ = _load_profile(self._profile)
211
+ if self._cred_file.is_file():
212
+ cfg = configparser.ConfigParser(interpolation=None)
213
+ cfg.read(self._cred_file)
214
+ if cfg.has_section(self._profile):
215
+ merged.update(dict(cfg.items(self._profile)))
216
+ if not merged:
217
+ raise IdentityNotFound(
218
+ f"profile {self._profile!r} not found in credentials/config files"
219
+ )
220
+ return merged
221
+
222
+
223
+ class EcsContainerCredentialsProvider(CredentialsProvider):
224
+ """Resolve credentials from the ECS/EKS container credentials endpoint."""
225
+
226
+ def __init__(self, client: Client | AsyncClient) -> None:
227
+ self._client = client
228
+
229
+ def resolve_identity(self) -> Credentials:
230
+ if isinstance(self._client, AsyncClient):
231
+ raise TypeError(
232
+ "EcsContainerCredentialsProvider configured with AsyncClient; use aresolve_identity"
233
+ )
234
+ url, headers = self._request_args()
235
+ resp = self._client.get(url, headers=headers)
236
+ if resp.status < 200 or resp.status >= 300:
237
+ raise IdentityNotFound(
238
+ f"ECS credentials endpoint returned status {resp.status}"
239
+ )
240
+ return _credentials_from_json(resp.json)
241
+
242
+ async def aresolve_identity(self) -> Credentials:
243
+ if not isinstance(self._client, AsyncClient):
244
+ raise TypeError(
245
+ "EcsContainerCredentialsProvider configured with sync Client; use resolve_identity"
246
+ )
247
+ url, headers = self._request_args()
248
+ resp = await self._client.get(url, headers=headers)
249
+ if resp.status < 200 or resp.status >= 300:
250
+ raise IdentityNotFound(
251
+ f"ECS credentials endpoint returned status {resp.status}"
252
+ )
253
+ return _credentials_from_json(resp.json)
254
+
255
+ def _request_args(self) -> tuple[str, dict[str, str]]:
256
+ relative = os.environ.get("AWS_CONTAINER_CREDENTIALS_RELATIVE_URI")
257
+ full = os.environ.get("AWS_CONTAINER_CREDENTIALS_FULL_URI")
258
+ if relative:
259
+ url = "http://169.254.170.2" + relative
260
+ elif full:
261
+ url = full
262
+ else:
263
+ raise IdentityNotFound("no ECS container credentials env var set")
264
+ headers: dict[str, str] = {}
265
+ token = os.environ.get("AWS_CONTAINER_AUTHORIZATION_TOKEN")
266
+ token_file = os.environ.get("AWS_CONTAINER_AUTHORIZATION_TOKEN_FILE")
267
+ if token_file:
268
+ token = Path(token_file).read_text().strip()
269
+ if token:
270
+ headers["Authorization"] = token
271
+ return url, headers
272
+
273
+
274
+ class Ec2InstanceMetadataProvider(CredentialsProvider):
275
+ """Resolve credentials from the EC2 Instance Metadata Service (IMDSv2)."""
276
+
277
+ _BASE = "http://169.254.169.254"
278
+ _TOKEN_PATH = "/latest/api/token"
279
+ _CREDS_PATH = "/latest/meta-data/iam/security-credentials/"
280
+
281
+ def __init__(self, client: Client | AsyncClient) -> None:
282
+ self._client = client
283
+
284
+ def resolve_identity(self) -> Credentials:
285
+ if isinstance(self._client, AsyncClient):
286
+ raise TypeError(
287
+ "Ec2InstanceMetadataProvider configured with AsyncClient; use aresolve_identity"
288
+ )
289
+ if os.environ.get("AWS_EC2_METADATA_DISABLED", "").strip().lower() in (
290
+ "true",
291
+ "1",
292
+ ):
293
+ raise IdentityNotFound("IMDS disabled via AWS_EC2_METADATA_DISABLED")
294
+ try:
295
+ token_resp = self._client.put(
296
+ self._BASE + self._TOKEN_PATH,
297
+ headers={"X-aws-ec2-metadata-token-ttl-seconds": "21600"},
298
+ body=b"",
299
+ )
300
+ auth = {"X-aws-ec2-metadata-token": token_resp.text}
301
+ role_resp = self._client.get(self._BASE + self._CREDS_PATH, headers=auth)
302
+ role = role_resp.text.strip()
303
+ creds_resp = self._client.get(
304
+ self._BASE + self._CREDS_PATH + role, headers=auth
305
+ )
306
+ except ZaprosError as e:
307
+ raise IdentityNotFound(f"IMDS request failed: {e}")
308
+ if creds_resp.status < 200 or creds_resp.status >= 300:
309
+ raise IdentityNotFound(f"IMDS returned status {creds_resp.status}")
310
+ return _credentials_from_json(creds_resp.json)
311
+
312
+ async def aresolve_identity(self) -> Credentials:
313
+ if not isinstance(self._client, AsyncClient):
314
+ raise TypeError(
315
+ "Ec2InstanceMetadataProvider configured with sync Client; use resolve_identity"
316
+ )
317
+ if os.environ.get("AWS_EC2_METADATA_DISABLED", "").strip().lower() in (
318
+ "true",
319
+ "1",
320
+ ):
321
+ raise IdentityNotFound("IMDS disabled via AWS_EC2_METADATA_DISABLED")
322
+ try:
323
+ token_resp = await self._client.put(
324
+ self._BASE + self._TOKEN_PATH,
325
+ headers={"X-aws-ec2-metadata-token-ttl-seconds": "21600"},
326
+ body=b"",
327
+ )
328
+ auth = {"X-aws-ec2-metadata-token": token_resp.text}
329
+ role_resp = await self._client.get(
330
+ self._BASE + self._CREDS_PATH, headers=auth
331
+ )
332
+ role = role_resp.text.strip()
333
+ creds_resp = await self._client.get(
334
+ self._BASE + self._CREDS_PATH + role, headers=auth
335
+ )
336
+ except ZaprosError as e:
337
+ raise IdentityNotFound(f"IMDS request failed: {e}")
338
+ if creds_resp.status < 200 or creds_resp.status >= 300:
339
+ raise IdentityNotFound(f"IMDS returned status {creds_resp.status}")
340
+ return _credentials_from_json(creds_resp.json)
341
+
342
+
343
+ @dataclass(frozen=True)
344
+ class SsoConfig:
345
+ start_url: str
346
+ region: str
347
+ account_id: str
348
+ role_name: str
349
+ session_name: str | None
350
+
351
+
352
+ class SsoCredentialsProvider(CredentialsProvider):
353
+ """Resolve credentials from AWS IAM Identity Center (SSO).
354
+
355
+ Reads the profile's ``sso_*`` settings, loads the access token that
356
+ ``aws sso login`` cached under ``~/.aws/sso/cache`` (refreshing it through
357
+ SSO-OIDC when it is about to expire) and exchanges it for short-term
358
+ credentials through the SSO ``GetRoleCredentials`` API.
359
+ """
360
+
361
+ def __init__(
362
+ self, client: Client | AsyncClient, profile: str | None = None
363
+ ) -> None:
364
+ self._client = client
365
+ self._profile = profile
366
+
367
+ def resolve_identity(self) -> Credentials:
368
+ if isinstance(self._client, AsyncClient):
369
+ raise TypeError(
370
+ "SsoCredentialsProvider configured with AsyncClient; use aresolve_identity"
371
+ )
372
+ # resolve the config first so an unconfigured profile just skips SSO
373
+ config = load_sso_config(self._profile)
374
+ require_dependency(capo_sso, "capo-sso")
375
+ token = load_sso_token(config, self._client)
376
+ sso = capo_sso.SSOClient(http_handler=self._client.handler)
377
+ try:
378
+ response = sso.get_role_credentials(
379
+ config.role_name,
380
+ config.account_id,
381
+ token,
382
+ config_overrides={
383
+ "region": config.region,
384
+ "credentials_provider": None,
385
+ },
386
+ )
387
+ except capo_sso.errors.UnauthorizedException as e:
388
+ raise SSOError(f"SSO token rejected; run `aws sso login`: {e}")
389
+ return credentials_from_role_credentials(response)
390
+
391
+ async def aresolve_identity(self) -> Credentials:
392
+ if not isinstance(self._client, AsyncClient):
393
+ raise TypeError(
394
+ "SsoCredentialsProvider configured with sync Client; use resolve_identity"
395
+ )
396
+ # resolve the config first so an unconfigured profile just skips SSO
397
+ config = load_sso_config(self._profile)
398
+ require_dependency(capo_sso, "capo-sso")
399
+ token = await aload_sso_token(config, self._client)
400
+ sso = capo_sso.AsyncSSOClient(http_handler=self._client.handler)
401
+ try:
402
+ response = await sso.get_role_credentials(
403
+ config.role_name,
404
+ config.account_id,
405
+ token,
406
+ config_overrides={
407
+ "region": config.region,
408
+ "credentials_provider": None,
409
+ },
410
+ )
411
+ except capo_sso.errors.UnauthorizedException as e:
412
+ raise SSOError(f"SSO token rejected; run `aws sso login`: {e}")
413
+ return credentials_from_role_credentials(response)
414
+
415
+
416
+ def load_config_section(name: str) -> dict[str, str]:
417
+ cfg_file = config_file()
418
+ if not cfg_file.is_file():
419
+ return {}
420
+ cfg = configparser.ConfigParser(interpolation=None)
421
+ cfg.read(cfg_file)
422
+ return dict(cfg.items(name)) if cfg.has_section(name) else {}
423
+
424
+
425
+ def load_sso_config(profile_name: str | None = None) -> SsoConfig:
426
+ profile, _ = _load_profile(profile_name)
427
+ session_name = profile.get("sso_session")
428
+ if session_name is None and not any(k.startswith("sso_") for k in profile):
429
+ raise IdentityNotFound("profile has no sso_* settings")
430
+ if session_name is not None:
431
+ # `sso_session` moves the portal settings into an [sso-session NAME] section
432
+ session = load_config_section(f"sso-session {session_name}")
433
+ start_url = session.get("sso_start_url")
434
+ region = session.get("sso_region")
435
+ else:
436
+ start_url = profile.get("sso_start_url")
437
+ region = profile.get("sso_region")
438
+ account_id = profile.get("sso_account_id")
439
+ role_name = profile.get("sso_role_name")
440
+ missing = [
441
+ name
442
+ for name, value in (
443
+ ("sso_start_url", start_url),
444
+ ("sso_region", region),
445
+ ("sso_account_id", account_id),
446
+ ("sso_role_name", role_name),
447
+ )
448
+ if not value
449
+ ]
450
+ if missing:
451
+ raise SSOError("incomplete sso configuration: missing " + ", ".join(missing))
452
+ assert start_url and region and account_id and role_name # narrowed by `missing`
453
+ return SsoConfig(
454
+ start_url=start_url,
455
+ region=region,
456
+ account_id=account_id,
457
+ role_name=role_name,
458
+ session_name=session_name,
459
+ )
460
+
461
+
462
+ def read_sso_token_cache(config: SsoConfig) -> tuple[Path, dict[str, object]]:
463
+ # cache file is sha1 of the session name (new format) or the start url (legacy)
464
+ key = config.session_name or config.start_url
465
+ digest = hashlib.sha1(key.encode("utf-8")).hexdigest()
466
+ path = Path.home() / ".aws" / "sso" / "cache" / f"{digest}.json"
467
+ if not path.is_file():
468
+ raise SSOError(f"no cached SSO token at {path}; run `aws sso login`")
469
+ data = json.loads(path.read_text())
470
+ if not isinstance(data.get("accessToken"), str) or not isinstance(
471
+ data.get("expiresAt"), str
472
+ ):
473
+ raise SSOError(f"SSO token cache {path} has no accessToken/expiresAt")
474
+ return path, data
475
+
476
+
477
+ def unexpired_sso_token(data: dict[str, object], *, skew: timedelta) -> str | None:
478
+ """The cached token if it is still good for at least ``skew``, else None."""
479
+ expires_at = parse_sso_expiry(str(data["expiresAt"]))
480
+ if expires_at - datetime.now(timezone.utc) <= skew:
481
+ return None
482
+ return str(data["accessToken"])
483
+
484
+
485
+ def sso_refresh_args(data: dict[str, object]) -> tuple[str, str, str] | None:
486
+ """Client registration + refresh token, if this entry can be refreshed.
487
+
488
+ Only tokens cached for an ``sso_session`` carry a client registration; the
489
+ legacy ``sso_start_url`` format has nothing to refresh with.
490
+ """
491
+ client_id = data.get("clientId")
492
+ client_secret = data.get("clientSecret")
493
+ refresh_token = data.get("refreshToken")
494
+ if (
495
+ not isinstance(client_id, str)
496
+ or not isinstance(client_secret, str)
497
+ or not isinstance(refresh_token, str)
498
+ ):
499
+ return None
500
+ registration_expires_at = data.get("registrationExpiresAt")
501
+ if isinstance(registration_expires_at, str):
502
+ if parse_sso_expiry(registration_expires_at) <= datetime.now(timezone.utc):
503
+ return None
504
+ return client_id, client_secret, refresh_token
505
+
506
+
507
+ def load_sso_token(config: SsoConfig, client: Client) -> str:
508
+ path, data = read_sso_token_cache(config)
509
+ fresh = unexpired_sso_token(data, skew=SSO_TOKEN_REFRESH_WINDOW)
510
+ if fresh is not None:
511
+ return fresh
512
+ refresh_args = sso_refresh_args(data)
513
+ if refresh_args is not None:
514
+ require_dependency(capo_sso_oidc, "capo-sso-oidc")
515
+ oidc = capo_sso_oidc.SSOOIDCClient(http_handler=client.handler)
516
+ try:
517
+ response = oidc.create_token(
518
+ refresh_args[0],
519
+ refresh_args[1],
520
+ "refresh_token",
521
+ refresh_token=refresh_args[2],
522
+ config_overrides={
523
+ "region": config.region,
524
+ "credentials_provider": None,
525
+ },
526
+ )
527
+ except (capo_sso_oidc.errors.SSOOIDCError, ZaprosError):
528
+ return sso_token_or_error(data, path)
529
+ return store_refreshed_sso_token(path, data, response)
530
+ return sso_token_or_error(data, path)
531
+
532
+
533
+ async def aload_sso_token(config: SsoConfig, client: AsyncClient) -> str:
534
+ path, data = read_sso_token_cache(config)
535
+ fresh = unexpired_sso_token(data, skew=SSO_TOKEN_REFRESH_WINDOW)
536
+ if fresh is not None:
537
+ return fresh
538
+ refresh_args = sso_refresh_args(data)
539
+ if refresh_args is not None:
540
+ require_dependency(capo_sso_oidc, "capo-sso-oidc")
541
+ oidc = capo_sso_oidc.AsyncSSOOIDCClient(http_handler=client.handler)
542
+ try:
543
+ response = await oidc.create_token(
544
+ refresh_args[0],
545
+ refresh_args[1],
546
+ "refresh_token",
547
+ refresh_token=refresh_args[2],
548
+ config_overrides={
549
+ "region": config.region,
550
+ "credentials_provider": None,
551
+ },
552
+ )
553
+ except (capo_sso_oidc.errors.SSOOIDCError, ZaprosError):
554
+ return sso_token_or_error(data, path)
555
+ return store_refreshed_sso_token(path, data, response)
556
+ return sso_token_or_error(data, path)
557
+
558
+
559
+ def sso_token_or_error(data: dict[str, object], path: Path) -> str:
560
+ """Fall back to the cached token when refreshing was impossible or failed."""
561
+ token = unexpired_sso_token(data, skew=timedelta(0))
562
+ if token is None:
563
+ raise SSOError(
564
+ f"cached SSO token in {path} expired at {data['expiresAt']}; "
565
+ "run `aws sso login`"
566
+ )
567
+ return token
568
+
569
+
570
+ def store_refreshed_sso_token(
571
+ path: Path,
572
+ data: dict[str, object],
573
+ response: capo_sso_oidc.types.create_token_response.CreateTokenResponse,
574
+ ) -> str:
575
+ access_token = response.get("access_token")
576
+ if not access_token:
577
+ raise SSOError("CreateToken response has no accessToken")
578
+ expires_at = datetime.now(timezone.utc) + timedelta(
579
+ seconds=response.get("expires_in", 0)
580
+ )
581
+ updated = dict(data)
582
+ updated["accessToken"] = access_token
583
+ updated["expiresAt"] = expires_at.strftime("%Y-%m-%dT%H:%M:%SZ")
584
+ refresh_token = response.get("refresh_token")
585
+ if refresh_token:
586
+ updated["refreshToken"] = refresh_token
587
+ path.write_text(json.dumps(updated))
588
+ path.chmod(0o600)
589
+ return access_token
590
+
591
+
592
+ def parse_sso_expiry(value: str) -> datetime:
593
+ # the CLI writes either an ISO offset, a trailing 'Z', or a trailing 'UTC'
594
+ parsed = datetime.fromisoformat(value.removesuffix("UTC").removesuffix("Z"))
595
+ if parsed.tzinfo is None:
596
+ parsed = parsed.replace(tzinfo=timezone.utc)
597
+ return parsed
598
+
599
+
600
+ def credentials_from_role_credentials(
601
+ response: capo_sso.types.get_role_credentials_response.GetRoleCredentialsResponse,
602
+ ) -> Credentials:
603
+ role = response.get("role_credentials") or {}
604
+ ak = role.get("access_key_id")
605
+ sk = role.get("secret_access_key")
606
+ if not ak or not sk:
607
+ raise SSOError("GetRoleCredentials response missing access key / secret key")
608
+ out: Credentials = {"access_key": ak, "secret_key": sk}
609
+ token = role.get("session_token")
610
+ if token:
611
+ out["session_token"] = token
612
+ expiration = role.get("expiration")
613
+ if expiration:
614
+ # SSO reports the expiry in epoch milliseconds
615
+ out["expiration"] = datetime.fromtimestamp(expiration / 1000, timezone.utc)
616
+ return out
617
+
618
+
619
+ @dataclass(frozen=True)
620
+ class AssumeRoleConfig:
621
+ role_arn: str
622
+ session_name: str
623
+ region: str
624
+ external_id: str | None
625
+ duration_seconds: int | None
626
+
627
+
628
+ class AssumeRoleCredentialsProvider(CredentialsProvider):
629
+ """Assume the profile's ``role_arn`` through STS ``AssumeRole``.
630
+
631
+ The credentials used to make the call come from the profile's
632
+ ``source_profile`` (which may itself be an SSO profile, or another
633
+ ``role_arn`` profile for role chaining) or from ``credential_source``.
634
+ """
635
+
636
+ def __init__(
637
+ self,
638
+ client: Client | AsyncClient,
639
+ profile: str | None = None,
640
+ _visited: frozenset[str] = frozenset(),
641
+ ) -> None:
642
+ self._client = client
643
+ self._profile = profile
644
+ self._visited = _visited
645
+
646
+ def resolve_identity(self) -> Credentials:
647
+ if isinstance(self._client, AsyncClient):
648
+ raise TypeError(
649
+ "AssumeRoleCredentialsProvider configured with AsyncClient; use aresolve_identity"
650
+ )
651
+ # resolve the config first so a profile without role_arn just skips
652
+ section, _ = _load_profile(self._profile)
653
+ config = assume_role_config(section)
654
+ require_dependency(capo_sts, "capo-sts")
655
+ source = self._source_provider(section)
656
+ sts = capo_sts.STSClient(
657
+ http_handler=self._client.handler,
658
+ region=config.region,
659
+ credentials_provider=cast("capo_sts.CredentialsProvider", source),
660
+ )
661
+ response = sts.assume_role(
662
+ config.role_arn,
663
+ config.session_name,
664
+ external_id=config.external_id,
665
+ duration_seconds=config.duration_seconds,
666
+ )
667
+ return credentials_from_sts(response.get("credentials"))
668
+
669
+ async def aresolve_identity(self) -> Credentials:
670
+ if not isinstance(self._client, AsyncClient):
671
+ raise TypeError(
672
+ "AssumeRoleCredentialsProvider configured with sync Client; use resolve_identity"
673
+ )
674
+ # resolve the config first so a profile without role_arn just skips
675
+ section, _ = _load_profile(self._profile)
676
+ config = assume_role_config(section)
677
+ require_dependency(capo_sts, "capo-sts")
678
+ source = self._source_provider(section)
679
+ sts = capo_sts.AsyncSTSClient(
680
+ http_handler=self._client.handler,
681
+ region=config.region,
682
+ credentials_provider=cast("capo_sts.CredentialsProvider", source),
683
+ )
684
+ response = await sts.assume_role(
685
+ config.role_arn,
686
+ config.session_name,
687
+ external_id=config.external_id,
688
+ duration_seconds=config.duration_seconds,
689
+ )
690
+ return credentials_from_sts(response.get("credentials"))
691
+
692
+ def _source_provider(
693
+ self, section: dict[str, str]
694
+ ) -> IdentityProvider[Credentials]:
695
+ source_profile = section.get("source_profile")
696
+ credential_source = section.get("credential_source")
697
+ if source_profile and credential_source:
698
+ raise AssumeRoleError(
699
+ "source_profile and credential_source are mutually exclusive"
700
+ )
701
+ if credential_source:
702
+ match credential_source:
703
+ case "Environment":
704
+ return EnvCredentialsProvider()
705
+ case "Ec2InstanceMetadata":
706
+ return Ec2InstanceMetadataProvider(self._client)
707
+ case "EcsContainer":
708
+ return EcsContainerCredentialsProvider(self._client)
709
+ case _:
710
+ raise AssumeRoleError(
711
+ f"unsupported credential_source {credential_source!r}"
712
+ )
713
+ if source_profile in self._visited:
714
+ raise AssumeRoleError(
715
+ f"circular source_profile reference through {source_profile!r}"
716
+ )
717
+ assert source_profile is not None # assume_role_config rejects the empty case
718
+ return ChainedProvider(
719
+ AssumeRoleCredentialsProvider(
720
+ self._client, source_profile, self._visited | {source_profile}
721
+ ),
722
+ SsoCredentialsProvider(self._client, source_profile),
723
+ ProfileCredentialsProvider(profile=source_profile),
724
+ )
725
+
726
+
727
+ class WebIdentityCredentialsProvider(CredentialsProvider):
728
+ """Exchange an OIDC token for credentials via ``AssumeRoleWithWebIdentity``.
729
+
730
+ Reads ``AWS_WEB_IDENTITY_TOKEN_FILE``/``AWS_ROLE_ARN`` or the profile's
731
+ ``web_identity_token_file``/``role_arn`` — this is how EKS service accounts
732
+ (IRSA) obtain credentials.
733
+ """
734
+
735
+ def __init__(
736
+ self, client: Client | AsyncClient, profile: str | None = None
737
+ ) -> None:
738
+ self._client = client
739
+ self._profile = profile
740
+
741
+ def resolve_identity(self) -> Credentials:
742
+ if isinstance(self._client, AsyncClient):
743
+ raise TypeError(
744
+ "WebIdentityCredentialsProvider configured with AsyncClient; use aresolve_identity"
745
+ )
746
+ config, token = self._config()
747
+ require_dependency(capo_sts, "capo-sts")
748
+ sts = capo_sts.STSClient(
749
+ http_handler=self._client.handler, region=config.region
750
+ )
751
+ response = sts.assume_role_with_web_identity(
752
+ config.role_arn,
753
+ config.session_name,
754
+ token,
755
+ duration_seconds=config.duration_seconds,
756
+ config_overrides={"credentials_provider": None},
757
+ )
758
+ return credentials_from_sts(response.get("credentials"))
759
+
760
+ async def aresolve_identity(self) -> Credentials:
761
+ if not isinstance(self._client, AsyncClient):
762
+ raise TypeError(
763
+ "WebIdentityCredentialsProvider configured with sync Client; use resolve_identity"
764
+ )
765
+ config, token = self._config()
766
+ require_dependency(capo_sts, "capo-sts")
767
+ sts = capo_sts.AsyncSTSClient(
768
+ http_handler=self._client.handler, region=config.region
769
+ )
770
+ response = await sts.assume_role_with_web_identity(
771
+ config.role_arn,
772
+ config.session_name,
773
+ token,
774
+ duration_seconds=config.duration_seconds,
775
+ config_overrides={"credentials_provider": None},
776
+ )
777
+ return credentials_from_sts(response.get("credentials"))
778
+
779
+ def _config(self) -> tuple[AssumeRoleConfig, str]:
780
+ section, _ = _load_profile(self._profile)
781
+ token_file = os.environ.get("AWS_WEB_IDENTITY_TOKEN_FILE")
782
+ role_arn = os.environ.get("AWS_ROLE_ARN")
783
+ session_name = os.environ.get("AWS_ROLE_SESSION_NAME")
784
+ if not token_file or not role_arn:
785
+ # the env pair is all-or-nothing; otherwise fall back to the profile
786
+ token_file = section.get("web_identity_token_file")
787
+ role_arn = section.get("role_arn")
788
+ session_name = section.get("role_session_name")
789
+ if not token_file or not role_arn:
790
+ raise IdentityNotFound("no web identity token file / role arn configured")
791
+ path = Path(token_file).expanduser()
792
+ if not path.is_file():
793
+ raise AssumeRoleError(f"web identity token file {path} does not exist")
794
+ config = AssumeRoleConfig(
795
+ role_arn=role_arn,
796
+ session_name=session_name or default_session_name(),
797
+ region=sts_region(section),
798
+ external_id=None,
799
+ duration_seconds=int_or_none(section.get("duration_seconds")),
800
+ )
801
+ return config, path.read_text().strip()
802
+
803
+
804
+ def assume_role_config(section: dict[str, str]) -> AssumeRoleConfig:
805
+ role_arn = section.get("role_arn")
806
+ if not role_arn:
807
+ raise IdentityNotFound("profile has no role_arn")
808
+ if section.get("web_identity_token_file"):
809
+ # handled by WebIdentityCredentialsProvider, not AssumeRole
810
+ raise IdentityNotFound("profile uses web_identity_token_file")
811
+ if not section.get("source_profile") and not section.get("credential_source"):
812
+ raise IdentityNotFound(
813
+ "role_arn profile has no source_profile/credential_source"
814
+ )
815
+ if section.get("mfa_serial"):
816
+ raise AssumeRoleError("mfa_serial profiles are not supported")
817
+ return AssumeRoleConfig(
818
+ role_arn=role_arn,
819
+ session_name=section.get("role_session_name") or default_session_name(),
820
+ region=sts_region(section),
821
+ external_id=section.get("external_id"),
822
+ duration_seconds=int_or_none(section.get("duration_seconds")),
823
+ )
824
+
825
+
826
+ def default_session_name() -> str:
827
+ return f"capo-session-{int(time.time())}"
828
+
829
+
830
+ def sts_region(section: dict[str, str]) -> str:
831
+ return os.environ.get("AWS_REGION") or section.get("region") or "us-east-1"
832
+
833
+
834
+ def int_or_none(value: str | None) -> int | None:
835
+ return int(value) if value else None
836
+
837
+
838
+ def credentials_from_sts(
839
+ credentials: capo_sts.types.credentials.Credentials | None,
840
+ ) -> Credentials:
841
+ if credentials is None:
842
+ raise AssumeRoleError("STS response contained no credentials")
843
+ return {
844
+ "access_key": credentials["access_key_id"],
845
+ "secret_key": credentials["secret_access_key"],
846
+ "session_token": credentials["session_token"],
847
+ "expiration": credentials["expiration"],
848
+ }
849
+
850
+
851
+ def _parse_iso8601(value: str) -> datetime:
852
+ # tolerate trailing 'Z'
853
+ return datetime.fromisoformat(value.replace("Z", "+00:00"))
854
+
855
+
856
+ def _credentials_from_json(data: dict[str, object]) -> Credentials:
857
+ ak = data.get("AccessKeyId")
858
+ sk = data.get("SecretAccessKey")
859
+ if not isinstance(ak, str) or not isinstance(sk, str):
860
+ raise IdentityNotFound(
861
+ "credentials response missing AccessKeyId/SecretAccessKey"
862
+ )
863
+ out: Credentials = {"access_key": ak, "secret_key": sk}
864
+ token = data.get("Token")
865
+ if isinstance(token, str):
866
+ out["session_token"] = token
867
+ exp = data.get("Expiration")
868
+ if isinstance(exp, str):
869
+ out["expiration"] = _parse_iso8601(exp)
870
+ return out
871
+
872
+
873
+ def default_aws_credentials_chain(
874
+ client: Client | AsyncClient,
875
+ ) -> IdentityProvider[Credentials]:
876
+ return CachedProvider(
877
+ ChainedProvider(
878
+ EnvCredentialsProvider(),
879
+ AssumeRoleCredentialsProvider(client),
880
+ WebIdentityCredentialsProvider(client),
881
+ SsoCredentialsProvider(client),
882
+ ProfileCredentialsProvider(),
883
+ EcsContainerCredentialsProvider(client),
884
+ Ec2InstanceMetadataProvider(client),
885
+ )
886
+ )