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.
- capo_codedeploy/__init__.py +70 -0
- capo_codedeploy/_async.py +25 -0
- capo_codedeploy/_auth/_identity.py +16 -0
- capo_codedeploy/_auth/_providers.py +886 -0
- capo_codedeploy/_auth/_signers.py +88 -0
- capo_codedeploy/_auth/_sigv4.py +434 -0
- capo_codedeploy/_auth/_zapros_handler.py +80 -0
- capo_codedeploy/_iter.py +113 -0
- capo_codedeploy/_operations/code_deploy_20141006/add_tags_to_on_premises_instances.py +153 -0
- capo_codedeploy/_operations/code_deploy_20141006/batch_get_application_revisions.py +173 -0
- capo_codedeploy/_operations/code_deploy_20141006/batch_get_applications.py +163 -0
- capo_codedeploy/_operations/code_deploy_20141006/batch_get_deployment_groups.py +178 -0
- capo_codedeploy/_operations/code_deploy_20141006/batch_get_deployment_instances.py +178 -0
- capo_codedeploy/_operations/code_deploy_20141006/batch_get_deployment_targets.py +188 -0
- capo_codedeploy/_operations/code_deploy_20141006/batch_get_deployments.py +158 -0
- capo_codedeploy/_operations/code_deploy_20141006/batch_get_on_premises_instances.py +158 -0
- capo_codedeploy/_operations/code_deploy_20141006/continue_deployment.py +155 -0
- capo_codedeploy/_operations/code_deploy_20141006/create_application.py +175 -0
- capo_codedeploy/_operations/code_deploy_20141006/create_deployment.py +278 -0
- capo_codedeploy/_operations/code_deploy_20141006/create_deployment_config.py +185 -0
- capo_codedeploy/_operations/code_deploy_20141006/create_deployment_group.py +320 -0
- capo_codedeploy/_operations/code_deploy_20141006/delete_application.py +129 -0
- capo_codedeploy/_operations/code_deploy_20141006/delete_deployment_config.py +136 -0
- capo_codedeploy/_operations/code_deploy_20141006/delete_deployment_group.py +167 -0
- capo_codedeploy/_operations/code_deploy_20141006/delete_git_hub_account_token.py +166 -0
- capo_codedeploy/_operations/code_deploy_20141006/delete_resources_by_external_id.py +137 -0
- capo_codedeploy/_operations/code_deploy_20141006/deregister_on_premises_instance.py +126 -0
- capo_codedeploy/_operations/code_deploy_20141006/get_application.py +157 -0
- capo_codedeploy/_operations/code_deploy_20141006/get_application_revision.py +173 -0
- capo_codedeploy/_operations/code_deploy_20141006/get_deployment.py +157 -0
- capo_codedeploy/_operations/code_deploy_20141006/get_deployment_config.py +162 -0
- capo_codedeploy/_operations/code_deploy_20141006/get_deployment_group.py +179 -0
- capo_codedeploy/_operations/code_deploy_20141006/get_deployment_instance.py +177 -0
- capo_codedeploy/_operations/code_deploy_20141006/get_deployment_target.py +182 -0
- capo_codedeploy/_operations/code_deploy_20141006/get_on_premises_instance.py +157 -0
- capo_codedeploy/_operations/code_deploy_20141006/list_application_revisions.py +195 -0
- capo_codedeploy/_operations/code_deploy_20141006/list_applications.py +149 -0
- capo_codedeploy/_operations/code_deploy_20141006/list_deployment_configs.py +147 -0
- capo_codedeploy/_operations/code_deploy_20141006/list_deployment_groups.py +162 -0
- capo_codedeploy/_operations/code_deploy_20141006/list_deployment_instances.py +198 -0
- capo_codedeploy/_operations/code_deploy_20141006/list_deployment_targets.py +188 -0
- capo_codedeploy/_operations/code_deploy_20141006/list_deployments.py +199 -0
- capo_codedeploy/_operations/code_deploy_20141006/list_git_hub_account_token_names.py +157 -0
- capo_codedeploy/_operations/code_deploy_20141006/list_on_premises_instances.py +159 -0
- capo_codedeploy/_operations/code_deploy_20141006/list_tags_for_resource.py +157 -0
- capo_codedeploy/_operations/code_deploy_20141006/put_lifecycle_event_hook_execution_status.py +177 -0
- capo_codedeploy/_operations/code_deploy_20141006/register_application_revision.py +147 -0
- capo_codedeploy/_operations/code_deploy_20141006/register_on_premises_instance.py +166 -0
- capo_codedeploy/_operations/code_deploy_20141006/remove_tags_from_on_premises_instances.py +153 -0
- capo_codedeploy/_operations/code_deploy_20141006/skip_wait_time_for_instance_termination.py +146 -0
- capo_codedeploy/_operations/code_deploy_20141006/stop_deployment.py +172 -0
- capo_codedeploy/_operations/code_deploy_20141006/tag_resource.py +174 -0
- capo_codedeploy/_operations/code_deploy_20141006/untag_resource.py +174 -0
- capo_codedeploy/_operations/code_deploy_20141006/update_application.py +134 -0
- capo_codedeploy/_operations/code_deploy_20141006/update_deployment_group.py +310 -0
- capo_codedeploy/_pagination.py +21 -0
- capo_codedeploy/_protocol/__init__.py +1 -0
- capo_codedeploy/_protocol/errors.py +93 -0
- capo_codedeploy/_protocol/eventstream.py +238 -0
- capo_codedeploy/_protocol/serialize.py +47 -0
- capo_codedeploy/_protocol/xml.py +33 -0
- capo_codedeploy/_rule_engine/__init__.py +0 -0
- capo_codedeploy/_rule_engine/_aws_partition.py +160 -0
- capo_codedeploy/_rule_engine/_endpoint_rule_set.py +138 -0
- capo_codedeploy/_rule_engine/_endpoint_runtime.py +389 -0
- capo_codedeploy/_services/_aws_config.py +160 -0
- capo_codedeploy/_services/_pipeline.py +198 -0
- capo_codedeploy/_services/async_code_deploy.py +3278 -0
- capo_codedeploy/_services/code_deploy.py +3229 -0
- capo_codedeploy/errors/__init__.py +335 -0
- capo_codedeploy/errors/_base.py +94 -0
- capo_codedeploy/errors/alarms_limit_exceeded_exception.py +52 -0
- capo_codedeploy/errors/application_already_exists_exception.py +54 -0
- capo_codedeploy/errors/application_does_not_exist_exception.py +54 -0
- capo_codedeploy/errors/application_limit_exceeded_exception.py +54 -0
- capo_codedeploy/errors/application_name_required_exception.py +54 -0
- capo_codedeploy/errors/arn_not_supported_exception.py +52 -0
- capo_codedeploy/errors/batch_limit_exceeded_exception.py +52 -0
- capo_codedeploy/errors/bucket_name_filter_required_exception.py +54 -0
- capo_codedeploy/errors/deployment_already_completed_exception.py +54 -0
- capo_codedeploy/errors/deployment_config_already_exists_exception.py +54 -0
- capo_codedeploy/errors/deployment_config_does_not_exist_exception.py +54 -0
- capo_codedeploy/errors/deployment_config_in_use_exception.py +54 -0
- capo_codedeploy/errors/deployment_config_limit_exceeded_exception.py +54 -0
- capo_codedeploy/errors/deployment_config_name_required_exception.py +54 -0
- capo_codedeploy/errors/deployment_does_not_exist_exception.py +54 -0
- capo_codedeploy/errors/deployment_group_already_exists_exception.py +54 -0
- capo_codedeploy/errors/deployment_group_does_not_exist_exception.py +54 -0
- capo_codedeploy/errors/deployment_group_limit_exceeded_exception.py +54 -0
- capo_codedeploy/errors/deployment_group_name_required_exception.py +54 -0
- capo_codedeploy/errors/deployment_id_required_exception.py +54 -0
- capo_codedeploy/errors/deployment_is_not_in_ready_state_exception.py +54 -0
- capo_codedeploy/errors/deployment_limit_exceeded_exception.py +54 -0
- capo_codedeploy/errors/deployment_not_started_exception.py +54 -0
- capo_codedeploy/errors/deployment_target_does_not_exist_exception.py +54 -0
- capo_codedeploy/errors/deployment_target_id_required_exception.py +54 -0
- capo_codedeploy/errors/deployment_target_list_size_exceeded_exception.py +56 -0
- capo_codedeploy/errors/description_too_long_exception.py +52 -0
- capo_codedeploy/errors/ecs_service_mapping_limit_exceeded_exception.py +54 -0
- capo_codedeploy/errors/git_hub_account_token_does_not_exist_exception.py +54 -0
- capo_codedeploy/errors/git_hub_account_token_name_required_exception.py +54 -0
- capo_codedeploy/errors/iam_arn_required_exception.py +52 -0
- capo_codedeploy/errors/iam_session_arn_already_registered_exception.py +54 -0
- capo_codedeploy/errors/iam_user_arn_already_registered_exception.py +54 -0
- capo_codedeploy/errors/iam_user_arn_required_exception.py +52 -0
- capo_codedeploy/errors/instance_does_not_exist_exception.py +54 -0
- capo_codedeploy/errors/instance_id_required_exception.py +52 -0
- capo_codedeploy/errors/instance_limit_exceeded_exception.py +54 -0
- capo_codedeploy/errors/instance_name_already_registered_exception.py +54 -0
- capo_codedeploy/errors/instance_name_required_exception.py +54 -0
- capo_codedeploy/errors/instance_not_registered_exception.py +54 -0
- capo_codedeploy/errors/invalid_alarm_config_exception.py +52 -0
- capo_codedeploy/errors/invalid_application_name_exception.py +54 -0
- capo_codedeploy/errors/invalid_arn_exception.py +52 -0
- capo_codedeploy/errors/invalid_auto_rollback_config_exception.py +54 -0
- capo_codedeploy/errors/invalid_auto_scaling_group_exception.py +54 -0
- capo_codedeploy/errors/invalid_blue_green_deployment_configuration_exception.py +60 -0
- capo_codedeploy/errors/invalid_bucket_name_filter_exception.py +54 -0
- capo_codedeploy/errors/invalid_compute_platform_exception.py +54 -0
- capo_codedeploy/errors/invalid_deployed_state_filter_exception.py +54 -0
- capo_codedeploy/errors/invalid_deployment_config_name_exception.py +54 -0
- capo_codedeploy/errors/invalid_deployment_group_name_exception.py +54 -0
- capo_codedeploy/errors/invalid_deployment_id_exception.py +52 -0
- capo_codedeploy/errors/invalid_deployment_instance_type_exception.py +54 -0
- capo_codedeploy/errors/invalid_deployment_status_exception.py +54 -0
- capo_codedeploy/errors/invalid_deployment_style_exception.py +54 -0
- capo_codedeploy/errors/invalid_deployment_target_id_exception.py +54 -0
- capo_codedeploy/errors/invalid_deployment_wait_type_exception.py +54 -0
- capo_codedeploy/errors/invalid_ec2_tag_combination_exception.py +54 -0
- capo_codedeploy/errors/invalid_ec2_tag_exception.py +52 -0
- capo_codedeploy/errors/invalid_ecs_service_exception.py +52 -0
- capo_codedeploy/errors/invalid_external_id_exception.py +52 -0
- capo_codedeploy/errors/invalid_file_exists_behavior_exception.py +54 -0
- capo_codedeploy/errors/invalid_git_hub_account_token_exception.py +54 -0
- capo_codedeploy/errors/invalid_git_hub_account_token_name_exception.py +54 -0
- capo_codedeploy/errors/invalid_iam_session_arn_exception.py +54 -0
- capo_codedeploy/errors/invalid_iam_user_arn_exception.py +52 -0
- capo_codedeploy/errors/invalid_ignore_application_stop_failures_value_exception.py +60 -0
- capo_codedeploy/errors/invalid_input_exception.py +52 -0
- capo_codedeploy/errors/invalid_instance_name_exception.py +52 -0
- capo_codedeploy/errors/invalid_instance_status_exception.py +54 -0
- capo_codedeploy/errors/invalid_instance_type_exception.py +52 -0
- capo_codedeploy/errors/invalid_key_prefix_filter_exception.py +54 -0
- capo_codedeploy/errors/invalid_lifecycle_event_hook_execution_id_exception.py +60 -0
- capo_codedeploy/errors/invalid_lifecycle_event_hook_execution_status_exception.py +60 -0
- capo_codedeploy/errors/invalid_load_balancer_info_exception.py +54 -0
- capo_codedeploy/errors/invalid_minimum_healthy_host_value_exception.py +54 -0
- capo_codedeploy/errors/invalid_next_token_exception.py +52 -0
- capo_codedeploy/errors/invalid_on_premises_tag_combination_exception.py +56 -0
- capo_codedeploy/errors/invalid_operation_exception.py +52 -0
- capo_codedeploy/errors/invalid_registration_status_exception.py +54 -0
- capo_codedeploy/errors/invalid_revision_exception.py +52 -0
- capo_codedeploy/errors/invalid_role_exception.py +52 -0
- capo_codedeploy/errors/invalid_sort_by_exception.py +52 -0
- capo_codedeploy/errors/invalid_sort_order_exception.py +52 -0
- capo_codedeploy/errors/invalid_tag_exception.py +52 -0
- capo_codedeploy/errors/invalid_tag_filter_exception.py +52 -0
- capo_codedeploy/errors/invalid_tags_to_add_exception.py +52 -0
- capo_codedeploy/errors/invalid_target_filter_name_exception.py +54 -0
- capo_codedeploy/errors/invalid_target_group_pair_exception.py +54 -0
- capo_codedeploy/errors/invalid_target_instances_exception.py +54 -0
- capo_codedeploy/errors/invalid_time_range_exception.py +52 -0
- capo_codedeploy/errors/invalid_traffic_routing_configuration_exception.py +58 -0
- capo_codedeploy/errors/invalid_trigger_config_exception.py +54 -0
- capo_codedeploy/errors/invalid_update_outdated_instances_only_value_exception.py +60 -0
- capo_codedeploy/errors/invalid_zonal_deployment_configuration_exception.py +60 -0
- capo_codedeploy/errors/lifecycle_event_already_completed_exception.py +54 -0
- capo_codedeploy/errors/lifecycle_hook_limit_exceeded_exception.py +54 -0
- capo_codedeploy/errors/multiple_iam_arns_provided_exception.py +54 -0
- capo_codedeploy/errors/operation_not_supported_exception.py +54 -0
- capo_codedeploy/errors/resource_arn_required_exception.py +52 -0
- capo_codedeploy/errors/resource_validation_exception.py +52 -0
- capo_codedeploy/errors/revision_does_not_exist_exception.py +54 -0
- capo_codedeploy/errors/revision_required_exception.py +52 -0
- capo_codedeploy/errors/role_required_exception.py +52 -0
- capo_codedeploy/errors/tag_limit_exceeded_exception.py +52 -0
- capo_codedeploy/errors/tag_required_exception.py +52 -0
- capo_codedeploy/errors/tag_set_list_limit_exceeded_exception.py +54 -0
- capo_codedeploy/errors/throttling_exception.py +52 -0
- capo_codedeploy/errors/trigger_targets_limit_exceeded_exception.py +54 -0
- capo_codedeploy/errors/unsupported_action_for_deployment_type_exception.py +58 -0
- capo_codedeploy/py.typed +0 -0
- capo_codedeploy/types/_prelude/blob.py +12 -0
- capo_codedeploy/types/_prelude/timestamp.py +17 -0
- capo_codedeploy/types/add_tags_to_on_premises_instances_input.py +59 -0
- capo_codedeploy/types/additional_deployment_status_info.py +5 -0
- capo_codedeploy/types/alarm.py +28 -0
- capo_codedeploy/types/alarm_configuration.py +51 -0
- capo_codedeploy/types/alarm_list.py +29 -0
- capo_codedeploy/types/alarm_name.py +5 -0
- capo_codedeploy/types/app_spec_content.py +35 -0
- capo_codedeploy/types/application_id.py +5 -0
- capo_codedeploy/types/application_info.py +90 -0
- capo_codedeploy/types/application_name.py +5 -0
- capo_codedeploy/types/application_revision_sort_by.py +18 -0
- capo_codedeploy/types/applications_info_list.py +33 -0
- capo_codedeploy/types/applications_list.py +19 -0
- capo_codedeploy/types/arn.py +5 -0
- capo_codedeploy/types/auto_rollback_configuration.py +50 -0
- capo_codedeploy/types/auto_rollback_event.py +18 -0
- capo_codedeploy/types/auto_rollback_events_list.py +35 -0
- capo_codedeploy/types/auto_scaling_group.py +47 -0
- capo_codedeploy/types/auto_scaling_group_hook.py +5 -0
- capo_codedeploy/types/auto_scaling_group_list.py +35 -0
- capo_codedeploy/types/auto_scaling_group_name.py +5 -0
- capo_codedeploy/types/auto_scaling_group_name_list.py +19 -0
- capo_codedeploy/types/batch_get_application_revisions_input.py +55 -0
- capo_codedeploy/types/batch_get_application_revisions_output.py +56 -0
- capo_codedeploy/types/batch_get_applications_input.py +45 -0
- capo_codedeploy/types/batch_get_applications_output.py +42 -0
- capo_codedeploy/types/batch_get_deployment_groups_input.py +57 -0
- capo_codedeploy/types/batch_get_deployment_groups_output.py +49 -0
- capo_codedeploy/types/batch_get_deployment_instances_input.py +53 -0
- capo_codedeploy/types/batch_get_deployment_instances_output.py +49 -0
- capo_codedeploy/types/batch_get_deployment_targets_input.py +51 -0
- capo_codedeploy/types/batch_get_deployment_targets_output.py +42 -0
- capo_codedeploy/types/batch_get_deployments_input.py +43 -0
- capo_codedeploy/types/batch_get_deployments_output.py +42 -0
- capo_codedeploy/types/batch_get_on_premises_instances_input.py +45 -0
- capo_codedeploy/types/batch_get_on_premises_instances_output.py +42 -0
- capo_codedeploy/types/blue_green_deployment_configuration.py +84 -0
- capo_codedeploy/types/blue_instance_termination_option.py +46 -0
- capo_codedeploy/types/boolean.py +5 -0
- capo_codedeploy/types/bundle_type.py +20 -0
- capo_codedeploy/types/cloud_formation_resource_type.py +5 -0
- capo_codedeploy/types/cloud_formation_target.py +112 -0
- capo_codedeploy/types/commit_id.py +5 -0
- capo_codedeploy/types/compute_platform.py +18 -0
- capo_codedeploy/types/continue_deployment_input.py +49 -0
- capo_codedeploy/types/create_application_input.py +67 -0
- capo_codedeploy/types/create_application_output.py +28 -0
- capo_codedeploy/types/create_deployment_config_input.py +115 -0
- capo_codedeploy/types/create_deployment_config_output.py +30 -0
- capo_codedeploy/types/create_deployment_group_input.py +353 -0
- capo_codedeploy/types/create_deployment_group_output.py +30 -0
- capo_codedeploy/types/create_deployment_input.py +178 -0
- capo_codedeploy/types/create_deployment_output.py +28 -0
- capo_codedeploy/types/delete_application_input.py +31 -0
- capo_codedeploy/types/delete_deployment_config_input.py +35 -0
- capo_codedeploy/types/delete_deployment_group_input.py +45 -0
- capo_codedeploy/types/delete_deployment_group_output.py +42 -0
- capo_codedeploy/types/delete_git_hub_account_token_input.py +30 -0
- capo_codedeploy/types/delete_git_hub_account_token_output.py +30 -0
- capo_codedeploy/types/delete_resources_by_external_id_input.py +28 -0
- capo_codedeploy/types/delete_resources_by_external_id_output.py +18 -0
- capo_codedeploy/types/deployment_config_id.py +5 -0
- capo_codedeploy/types/deployment_config_info.py +134 -0
- capo_codedeploy/types/deployment_config_name.py +5 -0
- capo_codedeploy/types/deployment_configs_list.py +19 -0
- capo_codedeploy/types/deployment_creator.py +23 -0
- capo_codedeploy/types/deployment_group_id.py +5 -0
- capo_codedeploy/types/deployment_group_info.py +420 -0
- capo_codedeploy/types/deployment_group_info_list.py +35 -0
- capo_codedeploy/types/deployment_group_name.py +5 -0
- capo_codedeploy/types/deployment_groups_list.py +19 -0
- capo_codedeploy/types/deployment_id.py +5 -0
- capo_codedeploy/types/deployment_info.py +499 -0
- capo_codedeploy/types/deployment_option.py +17 -0
- capo_codedeploy/types/deployment_overview.py +64 -0
- capo_codedeploy/types/deployment_ready_action.py +17 -0
- capo_codedeploy/types/deployment_ready_option.py +50 -0
- capo_codedeploy/types/deployment_status.py +23 -0
- capo_codedeploy/types/deployment_status_list.py +33 -0
- capo_codedeploy/types/deployment_status_message_list.py +19 -0
- capo_codedeploy/types/deployment_style.py +61 -0
- capo_codedeploy/types/deployment_target.py +115 -0
- capo_codedeploy/types/deployment_target_list.py +33 -0
- capo_codedeploy/types/deployment_target_type.py +19 -0
- capo_codedeploy/types/deployment_type.py +17 -0
- capo_codedeploy/types/deployment_wait_type.py +17 -0
- capo_codedeploy/types/deployments_info_list.py +31 -0
- capo_codedeploy/types/deployments_list.py +17 -0
- capo_codedeploy/types/deregister_on_premises_instance_input.py +33 -0
- capo_codedeploy/types/description.py +5 -0
- capo_codedeploy/types/diagnostics.py +63 -0
- capo_codedeploy/types/duration.py +5 -0
- capo_codedeploy/types/e_tag.py +5 -0
- capo_codedeploy/types/ec2_tag_filter.py +52 -0
- capo_codedeploy/types/ec2_tag_filter_list.py +29 -0
- capo_codedeploy/types/ec2_tag_filter_type.py +18 -0
- capo_codedeploy/types/ec2_tag_set.py +42 -0
- capo_codedeploy/types/ec2_tag_set_list.py +35 -0
- capo_codedeploy/types/ecs_cluster_name.py +5 -0
- capo_codedeploy/types/ecs_service.py +35 -0
- capo_codedeploy/types/ecs_service_list.py +29 -0
- capo_codedeploy/types/ecs_service_name.py +5 -0
- capo_codedeploy/types/ecs_target.py +114 -0
- capo_codedeploy/types/ecs_task_set.py +111 -0
- capo_codedeploy/types/ecs_task_set_count.py +5 -0
- capo_codedeploy/types/ecs_task_set_identifier.py +5 -0
- capo_codedeploy/types/ecs_task_set_list.py +29 -0
- capo_codedeploy/types/ecs_task_set_status.py +5 -0
- capo_codedeploy/types/elb_info.py +28 -0
- capo_codedeploy/types/elb_info_list.py +29 -0
- capo_codedeploy/types/elb_name.py +5 -0
- capo_codedeploy/types/error_code.py +49 -0
- capo_codedeploy/types/error_information.py +43 -0
- capo_codedeploy/types/error_message.py +5 -0
- capo_codedeploy/types/external_id.py +5 -0
- capo_codedeploy/types/file_exists_behavior.py +18 -0
- capo_codedeploy/types/filter_value.py +5 -0
- capo_codedeploy/types/filter_value_list.py +17 -0
- capo_codedeploy/types/generic_revision_info.py +96 -0
- capo_codedeploy/types/get_application_input.py +31 -0
- capo_codedeploy/types/get_application_output.py +40 -0
- capo_codedeploy/types/get_application_revision_input.py +51 -0
- capo_codedeploy/types/get_application_revision_output.py +70 -0
- capo_codedeploy/types/get_deployment_config_input.py +35 -0
- capo_codedeploy/types/get_deployment_config_output.py +42 -0
- capo_codedeploy/types/get_deployment_group_input.py +43 -0
- capo_codedeploy/types/get_deployment_group_output.py +42 -0
- capo_codedeploy/types/get_deployment_input.py +31 -0
- capo_codedeploy/types/get_deployment_instance_input.py +39 -0
- capo_codedeploy/types/get_deployment_instance_output.py +42 -0
- capo_codedeploy/types/get_deployment_output.py +40 -0
- capo_codedeploy/types/get_deployment_target_input.py +39 -0
- capo_codedeploy/types/get_deployment_target_output.py +42 -0
- capo_codedeploy/types/get_on_premises_instance_input.py +31 -0
- capo_codedeploy/types/get_on_premises_instance_output.py +40 -0
- capo_codedeploy/types/git_hub_account_token_name.py +5 -0
- capo_codedeploy/types/git_hub_account_token_name_list.py +19 -0
- capo_codedeploy/types/git_hub_location.py +35 -0
- capo_codedeploy/types/green_fleet_provisioning_action.py +17 -0
- capo_codedeploy/types/green_fleet_provisioning_option.py +42 -0
- capo_codedeploy/types/iam_session_arn.py +5 -0
- capo_codedeploy/types/iam_user_arn.py +5 -0
- capo_codedeploy/types/instance_action.py +17 -0
- capo_codedeploy/types/instance_arn.py +5 -0
- capo_codedeploy/types/instance_count.py +5 -0
- capo_codedeploy/types/instance_id.py +5 -0
- capo_codedeploy/types/instance_info.py +95 -0
- capo_codedeploy/types/instance_info_list.py +29 -0
- capo_codedeploy/types/instance_name.py +5 -0
- capo_codedeploy/types/instance_name_list.py +17 -0
- capo_codedeploy/types/instance_status.py +22 -0
- capo_codedeploy/types/instance_status_list.py +31 -0
- capo_codedeploy/types/instance_summary.py +107 -0
- capo_codedeploy/types/instance_summary_list.py +33 -0
- capo_codedeploy/types/instance_target.py +112 -0
- capo_codedeploy/types/instance_type.py +17 -0
- capo_codedeploy/types/instance_type_list.py +29 -0
- capo_codedeploy/types/instances_list.py +17 -0
- capo_codedeploy/types/key.py +5 -0
- capo_codedeploy/types/lambda_function_alias.py +5 -0
- capo_codedeploy/types/lambda_function_info.py +69 -0
- capo_codedeploy/types/lambda_function_name.py +5 -0
- capo_codedeploy/types/lambda_target.py +114 -0
- capo_codedeploy/types/last_deployment_info.py +74 -0
- capo_codedeploy/types/lifecycle_error_code.py +21 -0
- capo_codedeploy/types/lifecycle_event.py +95 -0
- capo_codedeploy/types/lifecycle_event_hook_execution_id.py +5 -0
- capo_codedeploy/types/lifecycle_event_list.py +31 -0
- capo_codedeploy/types/lifecycle_event_name.py +5 -0
- capo_codedeploy/types/lifecycle_event_status.py +21 -0
- capo_codedeploy/types/lifecycle_message.py +5 -0
- capo_codedeploy/types/list_application_revisions_input.py +111 -0
- capo_codedeploy/types/list_application_revisions_output.py +49 -0
- capo_codedeploy/types/list_applications_input.py +28 -0
- capo_codedeploy/types/list_applications_output.py +49 -0
- capo_codedeploy/types/list_deployment_configs_input.py +28 -0
- capo_codedeploy/types/list_deployment_configs_output.py +49 -0
- capo_codedeploy/types/list_deployment_groups_input.py +40 -0
- capo_codedeploy/types/list_deployment_groups_output.py +58 -0
- capo_codedeploy/types/list_deployment_instances_input.py +82 -0
- capo_codedeploy/types/list_deployment_instances_output.py +47 -0
- capo_codedeploy/types/list_deployment_targets_input.py +57 -0
- capo_codedeploy/types/list_deployment_targets_output.py +45 -0
- capo_codedeploy/types/list_deployments_input.py +93 -0
- capo_codedeploy/types/list_deployments_output.py +47 -0
- capo_codedeploy/types/list_git_hub_account_token_names_input.py +28 -0
- capo_codedeploy/types/list_git_hub_account_token_names_output.py +49 -0
- capo_codedeploy/types/list_on_premises_instances_input.py +68 -0
- capo_codedeploy/types/list_on_premises_instances_output.py +49 -0
- capo_codedeploy/types/list_state_filter_action.py +18 -0
- capo_codedeploy/types/list_tags_for_resource_input.py +38 -0
- capo_codedeploy/types/list_tags_for_resource_output.py +43 -0
- capo_codedeploy/types/listener_arn.py +5 -0
- capo_codedeploy/types/listener_arn_list.py +17 -0
- capo_codedeploy/types/load_balancer_info.py +80 -0
- capo_codedeploy/types/log_tail.py +5 -0
- capo_codedeploy/types/message.py +5 -0
- capo_codedeploy/types/minimum_healthy_hosts.py +50 -0
- capo_codedeploy/types/minimum_healthy_hosts_per_zone.py +50 -0
- capo_codedeploy/types/minimum_healthy_hosts_per_zone_type.py +17 -0
- capo_codedeploy/types/minimum_healthy_hosts_per_zone_value.py +5 -0
- capo_codedeploy/types/minimum_healthy_hosts_type.py +17 -0
- capo_codedeploy/types/minimum_healthy_hosts_value.py +5 -0
- capo_codedeploy/types/next_token.py +5 -0
- capo_codedeploy/types/nullable_boolean.py +5 -0
- capo_codedeploy/types/on_premises_tag_set.py +42 -0
- capo_codedeploy/types/on_premises_tag_set_list.py +31 -0
- capo_codedeploy/types/outdated_instances_strategy.py +17 -0
- capo_codedeploy/types/percentage.py +5 -0
- capo_codedeploy/types/put_lifecycle_event_hook_execution_status_input.py +60 -0
- capo_codedeploy/types/put_lifecycle_event_hook_execution_status_output.py +32 -0
- capo_codedeploy/types/raw_string.py +35 -0
- capo_codedeploy/types/raw_string_content.py +5 -0
- capo_codedeploy/types/raw_string_sha256.py +5 -0
- capo_codedeploy/types/register_application_revision_input.py +58 -0
- capo_codedeploy/types/register_on_premises_instance_input.py +47 -0
- capo_codedeploy/types/registration_status.py +17 -0
- capo_codedeploy/types/related_deployments.py +55 -0
- capo_codedeploy/types/remove_tags_from_on_premises_instances_input.py +61 -0
- capo_codedeploy/types/repository.py +5 -0
- capo_codedeploy/types/revision_info.py +63 -0
- capo_codedeploy/types/revision_info_list.py +29 -0
- capo_codedeploy/types/revision_location.py +114 -0
- capo_codedeploy/types/revision_location_list.py +33 -0
- capo_codedeploy/types/revision_location_type.py +19 -0
- capo_codedeploy/types/role.py +5 -0
- capo_codedeploy/types/rollback_info.py +49 -0
- capo_codedeploy/types/s3_bucket.py +5 -0
- capo_codedeploy/types/s3_key.py +5 -0
- capo_codedeploy/types/s3_location.py +64 -0
- capo_codedeploy/types/script_name.py +5 -0
- capo_codedeploy/types/skip_wait_time_for_instance_termination_input.py +28 -0
- capo_codedeploy/types/sort_order.py +17 -0
- capo_codedeploy/types/stop_deployment_input.py +40 -0
- capo_codedeploy/types/stop_deployment_output.py +43 -0
- capo_codedeploy/types/stop_status.py +17 -0
- capo_codedeploy/types/tag.py +35 -0
- capo_codedeploy/types/tag_filter.py +50 -0
- capo_codedeploy/types/tag_filter_list.py +29 -0
- capo_codedeploy/types/tag_filter_type.py +18 -0
- capo_codedeploy/types/tag_key_list.py +17 -0
- capo_codedeploy/types/tag_list.py +29 -0
- capo_codedeploy/types/tag_resource_input.py +45 -0
- capo_codedeploy/types/tag_resource_output.py +18 -0
- capo_codedeploy/types/target_arn.py +5 -0
- capo_codedeploy/types/target_filter_name.py +17 -0
- capo_codedeploy/types/target_filters.py +40 -0
- capo_codedeploy/types/target_group_info.py +28 -0
- capo_codedeploy/types/target_group_info_list.py +33 -0
- capo_codedeploy/types/target_group_name.py +5 -0
- capo_codedeploy/types/target_group_pair_info.py +79 -0
- capo_codedeploy/types/target_group_pair_info_list.py +35 -0
- capo_codedeploy/types/target_id.py +5 -0
- capo_codedeploy/types/target_id_list.py +17 -0
- capo_codedeploy/types/target_instances.py +78 -0
- capo_codedeploy/types/target_label.py +17 -0
- capo_codedeploy/types/target_status.py +22 -0
- capo_codedeploy/types/time.py +20 -0
- capo_codedeploy/types/time_based_canary.py +37 -0
- capo_codedeploy/types/time_based_linear.py +37 -0
- capo_codedeploy/types/time_range.py +50 -0
- capo_codedeploy/types/timestamp.py +20 -0
- capo_codedeploy/types/traffic_route.py +42 -0
- capo_codedeploy/types/traffic_routing_config.py +80 -0
- capo_codedeploy/types/traffic_routing_type.py +18 -0
- capo_codedeploy/types/traffic_weight.py +5 -0
- capo_codedeploy/types/trigger_config.py +58 -0
- capo_codedeploy/types/trigger_config_list.py +31 -0
- capo_codedeploy/types/trigger_event_type.py +25 -0
- capo_codedeploy/types/trigger_event_type_list.py +35 -0
- capo_codedeploy/types/trigger_name.py +5 -0
- capo_codedeploy/types/trigger_target_arn.py +5 -0
- capo_codedeploy/types/untag_resource_input.py +47 -0
- capo_codedeploy/types/untag_resource_output.py +18 -0
- capo_codedeploy/types/update_application_input.py +38 -0
- capo_codedeploy/types/update_deployment_group_input.py +343 -0
- capo_codedeploy/types/update_deployment_group_output.py +42 -0
- capo_codedeploy/types/value.py +5 -0
- capo_codedeploy/types/version.py +5 -0
- capo_codedeploy/types/version_id.py +5 -0
- capo_codedeploy/types/wait_time_in_mins.py +5 -0
- capo_codedeploy/types/wait_time_in_seconds.py +5 -0
- capo_codedeploy/types/zonal_config.py +63 -0
- capo_codedeploy-0.1.0.dist-info/METADATA +102 -0
- capo_codedeploy-0.1.0.dist-info/RECORD +472 -0
- capo_codedeploy-0.1.0.dist-info/WHEEL +5 -0
- capo_codedeploy-0.1.0.dist-info/licenses/LICENSE +21 -0
- capo_codedeploy-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from abc import ABC, abstractmethod
|
|
4
|
+
from typing import Any, Generic, TypeVar
|
|
5
|
+
|
|
6
|
+
from zapros import Request
|
|
7
|
+
|
|
8
|
+
from capo_codedeploy._auth._identity import Credentials, Identity
|
|
9
|
+
from capo_codedeploy._auth._providers import IdentityProvider
|
|
10
|
+
from capo_codedeploy._auth._sigv4 import SigV4AuthContext, sign_sigv4
|
|
11
|
+
|
|
12
|
+
IdentityT = TypeVar("IdentityT", bound="Identity")
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class Signer(ABC, Generic[IdentityT]):
|
|
16
|
+
"""Per-request request signer. Holds an IdentityProvider plus static config."""
|
|
17
|
+
|
|
18
|
+
def __init__(self, provider: IdentityProvider[IdentityT]) -> None:
|
|
19
|
+
self.provider = provider
|
|
20
|
+
|
|
21
|
+
@abstractmethod
|
|
22
|
+
async def asign(self, req: Request) -> Request: ...
|
|
23
|
+
@abstractmethod
|
|
24
|
+
def sign(self, req: Request) -> Request: ...
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class SigV4Signer(Signer[Credentials]):
|
|
28
|
+
"""aws.auth#sigv4 — AWS Signature Version 4.
|
|
29
|
+
|
|
30
|
+
The full auth scheme (``name`` variant, ``signingName``, ``signingRegion``,
|
|
31
|
+
encoding/normalization flags) is provided by the caller — either from the
|
|
32
|
+
endpoint rule-set's ``authSchemes`` property or built by the generated
|
|
33
|
+
``get_signer`` from operation defaults.
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
def __init__(
|
|
37
|
+
self, provider: IdentityProvider[Credentials], *, auth_scheme: dict[str, Any]
|
|
38
|
+
) -> None:
|
|
39
|
+
super().__init__(provider)
|
|
40
|
+
self._auth_scheme = auth_scheme
|
|
41
|
+
|
|
42
|
+
async def asign(self, req: Request) -> Request:
|
|
43
|
+
creds = await self.provider.aresolve_identity()
|
|
44
|
+
ctx: SigV4AuthContext = {
|
|
45
|
+
"type": "sig_v4",
|
|
46
|
+
"access_key_id": creds["access_key"],
|
|
47
|
+
"secret_access_key": creds["secret_key"],
|
|
48
|
+
"session_token": creds.get("session_token"),
|
|
49
|
+
"signing_region": self._auth_scheme["signingRegion"],
|
|
50
|
+
"signing_name": self._auth_scheme["signingName"],
|
|
51
|
+
"disable_double_encoding": self._auth_scheme.get(
|
|
52
|
+
"disableDoubleEncoding", False
|
|
53
|
+
),
|
|
54
|
+
"disable_normalize_path": self._auth_scheme.get(
|
|
55
|
+
"disableNormalizePath", False
|
|
56
|
+
),
|
|
57
|
+
}
|
|
58
|
+
if req.body is None:
|
|
59
|
+
body: bytes | None = b""
|
|
60
|
+
elif isinstance(req.body, bytes):
|
|
61
|
+
body = req.body
|
|
62
|
+
else:
|
|
63
|
+
body = None
|
|
64
|
+
return sign_sigv4(req, ctx, body)
|
|
65
|
+
|
|
66
|
+
def sign(self, req: Request) -> Request:
|
|
67
|
+
creds = self.provider.resolve_identity()
|
|
68
|
+
ctx: SigV4AuthContext = {
|
|
69
|
+
"type": "sig_v4",
|
|
70
|
+
"access_key_id": creds["access_key"],
|
|
71
|
+
"secret_access_key": creds["secret_key"],
|
|
72
|
+
"session_token": creds.get("session_token"),
|
|
73
|
+
"signing_region": self._auth_scheme["signingRegion"],
|
|
74
|
+
"signing_name": self._auth_scheme["signingName"],
|
|
75
|
+
"disable_double_encoding": self._auth_scheme.get(
|
|
76
|
+
"disableDoubleEncoding", False
|
|
77
|
+
),
|
|
78
|
+
"disable_normalize_path": self._auth_scheme.get(
|
|
79
|
+
"disableNormalizePath", False
|
|
80
|
+
),
|
|
81
|
+
}
|
|
82
|
+
if req.body is None:
|
|
83
|
+
body: bytes | None = b""
|
|
84
|
+
elif isinstance(req.body, bytes):
|
|
85
|
+
body = req.body
|
|
86
|
+
else:
|
|
87
|
+
body = None
|
|
88
|
+
return sign_sigv4(req, ctx, body)
|
|
@@ -0,0 +1,434 @@
|
|
|
1
|
+
"""AWS Signature Version 4 — single-chunk signing.
|
|
2
|
+
|
|
3
|
+
Reference:
|
|
4
|
+
https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_sigv-create-signed-request.html
|
|
5
|
+
|
|
6
|
+
Verified byte-for-byte against ``botocore.auth.S3SigV4Auth`` / ``SigV4Auth``
|
|
7
|
+
across S3 GET/PUT/POST, query-string, session-token, and non-S3 (IAM)
|
|
8
|
+
canonicalization paths.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import datetime as _dt
|
|
14
|
+
import functools
|
|
15
|
+
import hashlib
|
|
16
|
+
import hmac
|
|
17
|
+
import re
|
|
18
|
+
from typing import Any, Literal, TypedDict
|
|
19
|
+
from urllib.parse import quote, unquote
|
|
20
|
+
|
|
21
|
+
import zapros
|
|
22
|
+
from pywhatwgurl import URLSearchParams
|
|
23
|
+
from zapros import Headers, Request
|
|
24
|
+
from zapros._utils import get_host_header_value
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def build_sigv4_auth_scheme(
|
|
28
|
+
signing_name: str, region: str | None, endpoint_scheme: dict[str, Any] | None = None
|
|
29
|
+
) -> dict[str, Any] | None:
|
|
30
|
+
"""Return the sigv4 auth scheme for ``signing_name``/``region``, with the
|
|
31
|
+
endpoint rule set's matching ``authSchemes`` entry overlaid on top.
|
|
32
|
+
|
|
33
|
+
The rule set only *modifies* signing properties of the resolved scheme
|
|
34
|
+
(Smithy rules engine, ``authSchemes``): keys it carries win, keys it omits
|
|
35
|
+
keep the operation defaults — IAM's global endpoint names only
|
|
36
|
+
``signingRegion``, for instance. Returns None when no signing region is
|
|
37
|
+
known from either source.
|
|
38
|
+
"""
|
|
39
|
+
scheme: dict[str, Any] = {
|
|
40
|
+
"name": "sigv4",
|
|
41
|
+
"signingName": signing_name,
|
|
42
|
+
"signingRegion": region,
|
|
43
|
+
"disableDoubleEncoding": False,
|
|
44
|
+
"disableNormalizePath": False,
|
|
45
|
+
}
|
|
46
|
+
if endpoint_scheme:
|
|
47
|
+
scheme.update(endpoint_scheme)
|
|
48
|
+
if scheme.get("signingRegion") is None:
|
|
49
|
+
return None
|
|
50
|
+
return scheme
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class SigV4AuthContext(TypedDict):
|
|
54
|
+
type: Literal["sig_v4"]
|
|
55
|
+
access_key_id: str
|
|
56
|
+
secret_access_key: str
|
|
57
|
+
session_token: str | None
|
|
58
|
+
signing_region: str
|
|
59
|
+
signing_name: str
|
|
60
|
+
# Rules-engine sigv4 auth-scheme flags (False = standard SigV4: normalize
|
|
61
|
+
# dot segments, double-encode the path). S3-family endpoint rulesets set
|
|
62
|
+
# ``disableDoubleEncoding`` so the path is signed exactly as sent.
|
|
63
|
+
disable_double_encoding: bool
|
|
64
|
+
disable_normalize_path: bool
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
_SIGV4_ALGORITHM = "AWS4-HMAC-SHA256"
|
|
68
|
+
_EMPTY_PAYLOAD_SHA256 = hashlib.sha256(b"").hexdigest()
|
|
69
|
+
|
|
70
|
+
# Headers excluded from the signed-headers set. Mirrors botocore's denylist:
|
|
71
|
+
# these are hop-by-hop / mutable in transit, so signing them would break
|
|
72
|
+
# proxies or duplicate values already added by the transport layer.
|
|
73
|
+
_UNSIGNED_HEADERS = frozenset(
|
|
74
|
+
{
|
|
75
|
+
"authorization",
|
|
76
|
+
"cache-control",
|
|
77
|
+
"connection",
|
|
78
|
+
"expect",
|
|
79
|
+
"from",
|
|
80
|
+
"keep-alive",
|
|
81
|
+
"max-forwards",
|
|
82
|
+
"pragma",
|
|
83
|
+
"referer",
|
|
84
|
+
"te",
|
|
85
|
+
"trailer",
|
|
86
|
+
"transfer-encoding",
|
|
87
|
+
"upgrade",
|
|
88
|
+
"user-agent",
|
|
89
|
+
"x-amzn-trace-id",
|
|
90
|
+
"content-length",
|
|
91
|
+
"accept",
|
|
92
|
+
"accept-encoding",
|
|
93
|
+
}
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
_MULTI_SPACE = re.compile(r" +")
|
|
97
|
+
|
|
98
|
+
# Services that require the payload hash to travel in ``x-amz-content-sha256``.
|
|
99
|
+
# Other services sign the hash into the canonical request without sending it.
|
|
100
|
+
_S3_SIGNING_NAMES = frozenset({"s3", "s3express", "s3-outposts", "s3-object-lambda"})
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def _uri_encode(value: str) -> str:
|
|
104
|
+
"""RFC 3986 percent-encoding using only the unreserved set as safe."""
|
|
105
|
+
return quote(value, safe="-_.~")
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def _remove_dot_segments(path: str) -> str:
|
|
109
|
+
"""RFC 3986 §5.2.4 dot-segment removal (mirrors botocore's normalize_url_path)."""
|
|
110
|
+
segments: list[str] = []
|
|
111
|
+
for seg in path.split("/"):
|
|
112
|
+
if not seg or seg == ".":
|
|
113
|
+
continue
|
|
114
|
+
if seg == "..":
|
|
115
|
+
if segments:
|
|
116
|
+
segments.pop()
|
|
117
|
+
else:
|
|
118
|
+
segments.append(seg)
|
|
119
|
+
first = "/" if path.startswith("/") else ""
|
|
120
|
+
last = "/" if path.endswith("/") and segments else ""
|
|
121
|
+
return first + "/".join(segments) + last
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def _canonical_path(path: str, *, double_encode: bool, normalize: bool) -> str:
|
|
125
|
+
"""Build CanonicalURI.
|
|
126
|
+
|
|
127
|
+
Per the SigV4 spec, every segment is URI-encoded; standard services
|
|
128
|
+
normalize dot segments and URI-encode each segment **twice**. The
|
|
129
|
+
rules-engine ``disableNormalizePath`` / ``disableDoubleEncoding`` flags
|
|
130
|
+
turn those steps off — S3-family services set both, so the path is
|
|
131
|
+
signed exactly as provided.
|
|
132
|
+
"""
|
|
133
|
+
if not path:
|
|
134
|
+
return "/"
|
|
135
|
+
if not path.startswith("/"):
|
|
136
|
+
path = "/" + path
|
|
137
|
+
if normalize:
|
|
138
|
+
path = _remove_dot_segments(path)
|
|
139
|
+
if not double_encode:
|
|
140
|
+
return path
|
|
141
|
+
decoded = unquote(path)
|
|
142
|
+
first = quote(decoded, safe="/~")
|
|
143
|
+
return quote(first, safe="/~")
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def _canonical_query(query: str) -> str:
|
|
147
|
+
"""Build CanonicalQueryString from a raw query string (with or without ``?``)."""
|
|
148
|
+
if not query:
|
|
149
|
+
return ""
|
|
150
|
+
if query.startswith("?"):
|
|
151
|
+
query = query[1:]
|
|
152
|
+
if not query:
|
|
153
|
+
return ""
|
|
154
|
+
sp = URLSearchParams(query)
|
|
155
|
+
encoded = sorted((_uri_encode(k), _uri_encode(v)) for k, v in sp.entries())
|
|
156
|
+
return "&".join(f"{k}={v}" for k, v in encoded)
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def _trim_header_value(value: str) -> str:
|
|
160
|
+
"""Trim leading/trailing whitespace and collapse internal whitespace runs.
|
|
161
|
+
|
|
162
|
+
Spec note: the canonical form treats sequential whitespace inside an
|
|
163
|
+
unquoted value as a single space. We do not parse quoted-string syntax;
|
|
164
|
+
the conservative collapse is correct for every header AWS actually signs.
|
|
165
|
+
"""
|
|
166
|
+
return _MULTI_SPACE.sub(" ", value.strip())
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
def _canonical_headers(headers: Headers) -> tuple[str, str]:
|
|
170
|
+
"""Return ``(canonical_headers, signed_headers)``."""
|
|
171
|
+
grouped: dict[str, list[str]] = {}
|
|
172
|
+
for name in headers:
|
|
173
|
+
lname = name.lower()
|
|
174
|
+
if lname in _UNSIGNED_HEADERS:
|
|
175
|
+
continue
|
|
176
|
+
grouped[lname] = [_trim_header_value(v) for v in headers.getall(name)]
|
|
177
|
+
|
|
178
|
+
signed = sorted(grouped)
|
|
179
|
+
canonical = "".join(f"{name}:{','.join(grouped[name])}\n" for name in signed)
|
|
180
|
+
return canonical, ";".join(signed)
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
def _build_canonical_request(
|
|
184
|
+
*,
|
|
185
|
+
method: str,
|
|
186
|
+
path: str,
|
|
187
|
+
query: str,
|
|
188
|
+
headers: Headers,
|
|
189
|
+
payload_hash: str,
|
|
190
|
+
double_encode: bool,
|
|
191
|
+
normalize: bool,
|
|
192
|
+
) -> tuple[str, str]:
|
|
193
|
+
canonical_uri = _canonical_path(
|
|
194
|
+
path, double_encode=double_encode, normalize=normalize
|
|
195
|
+
)
|
|
196
|
+
canonical_query = _canonical_query(query)
|
|
197
|
+
canonical_headers, signed_headers = _canonical_headers(headers)
|
|
198
|
+
canonical_request = (
|
|
199
|
+
f"{method}\n"
|
|
200
|
+
f"{canonical_uri}\n"
|
|
201
|
+
f"{canonical_query}\n"
|
|
202
|
+
f"{canonical_headers}\n"
|
|
203
|
+
f"{signed_headers}\n"
|
|
204
|
+
f"{payload_hash}"
|
|
205
|
+
)
|
|
206
|
+
return canonical_request, signed_headers
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
@functools.lru_cache(maxsize=8)
|
|
210
|
+
def _derive_signing_key(secret: str, date: str, region: str, service: str) -> bytes:
|
|
211
|
+
# The key depends only on the UTC date stamp (not the full timestamp),
|
|
212
|
+
# so it is reused for every request in the same day/region/service.
|
|
213
|
+
k_date = hmac.new(
|
|
214
|
+
b"AWS4" + secret.encode("utf-8"), date.encode("ascii"), hashlib.sha256
|
|
215
|
+
).digest()
|
|
216
|
+
k_region = hmac.new(k_date, region.encode("utf-8"), hashlib.sha256).digest()
|
|
217
|
+
k_service = hmac.new(k_region, service.encode("utf-8"), hashlib.sha256).digest()
|
|
218
|
+
return hmac.new(k_service, b"aws4_request", hashlib.sha256).digest()
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
def _amz_now() -> _dt.datetime:
|
|
222
|
+
return _dt.datetime.now(_dt.timezone.utc)
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
def _canonical_query_from_pairs(pairs: list[tuple[str, str]]) -> str:
|
|
226
|
+
"""CanonicalQueryString from raw (unencoded) key/value pairs."""
|
|
227
|
+
encoded = sorted((_uri_encode(k), _uri_encode(v)) for k, v in pairs)
|
|
228
|
+
return "&".join(f"{k}={v}" for k, v in encoded)
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
def sign_sigv4(
|
|
232
|
+
request: Request,
|
|
233
|
+
ctx: SigV4AuthContext,
|
|
234
|
+
body: bytes | None,
|
|
235
|
+
) -> Request:
|
|
236
|
+
"""Return a new ``Request`` carrying SigV4 single-chunk auth headers.
|
|
237
|
+
|
|
238
|
+
Pass ``body=None`` to sign with ``UNSIGNED-PAYLOAD`` (streaming requests).
|
|
239
|
+
The original ``request.body`` is forwarded unchanged in that case.
|
|
240
|
+
"""
|
|
241
|
+
service = ctx["signing_name"]
|
|
242
|
+
region = ctx["signing_region"]
|
|
243
|
+
|
|
244
|
+
headers = request.headers.copy()
|
|
245
|
+
|
|
246
|
+
# X-Amz-Date — honor caller-supplied value (allows deterministic tests).
|
|
247
|
+
existing_date = headers.get("X-Amz-Date")
|
|
248
|
+
if existing_date:
|
|
249
|
+
amz_date = existing_date
|
|
250
|
+
date_stamp = amz_date[:8]
|
|
251
|
+
else:
|
|
252
|
+
now = _amz_now()
|
|
253
|
+
amz_date = now.strftime("%Y%m%dT%H%M%SZ")
|
|
254
|
+
date_stamp = now.strftime("%Y%m%d")
|
|
255
|
+
headers["X-Amz-Date"] = amz_date
|
|
256
|
+
|
|
257
|
+
# Payload hash. For S3-family services, x-amz-content-sha256 is mandatory
|
|
258
|
+
# and must be set BEFORE computing the canonical request (it gets signed).
|
|
259
|
+
payload_hash = headers.get("X-Amz-Content-SHA256")
|
|
260
|
+
if payload_hash is None:
|
|
261
|
+
if body is None:
|
|
262
|
+
payload_hash = "UNSIGNED-PAYLOAD"
|
|
263
|
+
else:
|
|
264
|
+
payload_hash = (
|
|
265
|
+
hashlib.sha256(body).hexdigest() if body else _EMPTY_PAYLOAD_SHA256
|
|
266
|
+
)
|
|
267
|
+
if service in _S3_SIGNING_NAMES:
|
|
268
|
+
headers["X-Amz-Content-SHA256"] = payload_hash
|
|
269
|
+
|
|
270
|
+
# Session token (STS / assumed-role credentials).
|
|
271
|
+
session_token = ctx.get("session_token")
|
|
272
|
+
if session_token:
|
|
273
|
+
headers["X-Amz-Security-Token"] = session_token
|
|
274
|
+
|
|
275
|
+
# Host header is added by Request.__init__ from the URL; defensive fallback.
|
|
276
|
+
if "host" not in headers and request.url.hostname:
|
|
277
|
+
headers["Host"] = get_host_header_value(request.url)
|
|
278
|
+
|
|
279
|
+
canonical_request, signed_headers = _build_canonical_request(
|
|
280
|
+
method=request.method.upper(),
|
|
281
|
+
path=request.url.pathname,
|
|
282
|
+
query=request.url.search,
|
|
283
|
+
headers=headers,
|
|
284
|
+
payload_hash=payload_hash,
|
|
285
|
+
double_encode=not ctx["disable_double_encoding"],
|
|
286
|
+
normalize=not ctx["disable_normalize_path"],
|
|
287
|
+
)
|
|
288
|
+
|
|
289
|
+
credential_scope = f"{date_stamp}/{region}/{service}/aws4_request"
|
|
290
|
+
string_to_sign = (
|
|
291
|
+
f"{_SIGV4_ALGORITHM}\n"
|
|
292
|
+
f"{amz_date}\n"
|
|
293
|
+
f"{credential_scope}\n"
|
|
294
|
+
f"{hashlib.sha256(canonical_request.encode('utf-8')).hexdigest()}"
|
|
295
|
+
)
|
|
296
|
+
|
|
297
|
+
signing_key = _derive_signing_key(
|
|
298
|
+
ctx["secret_access_key"], date_stamp, region, service
|
|
299
|
+
)
|
|
300
|
+
signature = hmac.new(
|
|
301
|
+
signing_key, string_to_sign.encode("utf-8"), hashlib.sha256
|
|
302
|
+
).hexdigest()
|
|
303
|
+
|
|
304
|
+
headers["Authorization"] = (
|
|
305
|
+
f"{_SIGV4_ALGORITHM} "
|
|
306
|
+
f"Credential={ctx['access_key_id']}/{credential_scope},"
|
|
307
|
+
f"SignedHeaders={signed_headers},"
|
|
308
|
+
f"Signature={signature}"
|
|
309
|
+
)
|
|
310
|
+
|
|
311
|
+
effective_body = body if body is not None else request.body
|
|
312
|
+
if effective_body is not None:
|
|
313
|
+
return Request(
|
|
314
|
+
request.url,
|
|
315
|
+
request.method,
|
|
316
|
+
headers,
|
|
317
|
+
body=effective_body,
|
|
318
|
+
context=request.context,
|
|
319
|
+
)
|
|
320
|
+
return Request(request.url, request.method, headers, context=request.context)
|
|
321
|
+
|
|
322
|
+
|
|
323
|
+
def presign_sigv4(
|
|
324
|
+
request: Request,
|
|
325
|
+
ctx: SigV4AuthContext,
|
|
326
|
+
*,
|
|
327
|
+
expires_in: int = 3600,
|
|
328
|
+
now: _dt.datetime | None = None,
|
|
329
|
+
) -> Request:
|
|
330
|
+
"""Return a new ``Request`` whose URL carries SigV4 query-string auth.
|
|
331
|
+
|
|
332
|
+
The signature travels in the URL (``X-Amz-*`` query params), so the result
|
|
333
|
+
is usable standalone (browser, curl). Payload is signed as
|
|
334
|
+
``UNSIGNED-PAYLOAD``, so the body is not bound by the signature.
|
|
335
|
+
|
|
336
|
+
``expires_in`` is the validity window in seconds; range 1..604800 (7 days),
|
|
337
|
+
bounded by the max lifetime of the derived signing key.
|
|
338
|
+
"""
|
|
339
|
+
if not 1 <= expires_in <= 604800:
|
|
340
|
+
raise ValueError(f"expires_in must be in [1, 604800], got {expires_in}")
|
|
341
|
+
|
|
342
|
+
sign_time = now or _amz_now()
|
|
343
|
+
if sign_time.tzinfo is None:
|
|
344
|
+
raise ValueError("now must be timezone-aware (UTC)")
|
|
345
|
+
|
|
346
|
+
service = ctx["signing_name"]
|
|
347
|
+
region = ctx["signing_region"]
|
|
348
|
+
|
|
349
|
+
amz_date = sign_time.strftime("%Y%m%dT%H%M%SZ")
|
|
350
|
+
date_stamp = sign_time.strftime("%Y%m%d")
|
|
351
|
+
credential_scope = f"{date_stamp}/{region}/{service}/aws4_request"
|
|
352
|
+
|
|
353
|
+
# Canonical headers: host is mandatory; anything else already on the request
|
|
354
|
+
# is signed too (and must then be sent alongside the URL). Strip the headers
|
|
355
|
+
# that belong in the query string to avoid header/query value conflicts
|
|
356
|
+
# (which AWS rejects as InvalidRequest).
|
|
357
|
+
headers = request.headers.copy()
|
|
358
|
+
for h in (
|
|
359
|
+
"Authorization",
|
|
360
|
+
"X-Amz-Date",
|
|
361
|
+
"X-Amz-Content-SHA256",
|
|
362
|
+
"X-Amz-Security-Token",
|
|
363
|
+
):
|
|
364
|
+
if h in headers:
|
|
365
|
+
del headers[h]
|
|
366
|
+
if "host" not in headers and request.url.hostname:
|
|
367
|
+
headers["Host"] = get_host_header_value(request.url)
|
|
368
|
+
|
|
369
|
+
canonical_headers, signed_headers = _canonical_headers(headers)
|
|
370
|
+
|
|
371
|
+
# Signed query params (raw values). X-Amz-Signature is appended afterwards.
|
|
372
|
+
amz_params: list[tuple[str, str]] = [
|
|
373
|
+
("X-Amz-Algorithm", _SIGV4_ALGORITHM),
|
|
374
|
+
("X-Amz-Credential", f"{ctx['access_key_id']}/{credential_scope}"),
|
|
375
|
+
("X-Amz-Date", amz_date),
|
|
376
|
+
("X-Amz-Expires", str(expires_in)),
|
|
377
|
+
("X-Amz-SignedHeaders", signed_headers),
|
|
378
|
+
]
|
|
379
|
+
session_token = ctx.get("session_token")
|
|
380
|
+
if session_token:
|
|
381
|
+
# S3 and most services require the token inside the canonical query.
|
|
382
|
+
# (A few — e.g. iotdevicegateway — want it appended post-signature
|
|
383
|
+
# instead; handle those as a special case if you ever target them.)
|
|
384
|
+
amz_params.append(("X-Amz-Security-Token", session_token))
|
|
385
|
+
|
|
386
|
+
existing = list(URLSearchParams(request.url.search).entries())
|
|
387
|
+
canonical_query = _canonical_query_from_pairs(existing + amz_params)
|
|
388
|
+
|
|
389
|
+
canonical_uri = _canonical_path(
|
|
390
|
+
request.url.pathname,
|
|
391
|
+
double_encode=not ctx["disable_double_encoding"],
|
|
392
|
+
normalize=not ctx["disable_normalize_path"],
|
|
393
|
+
)
|
|
394
|
+
canonical_request = (
|
|
395
|
+
f"{request.method.upper()}\n"
|
|
396
|
+
f"{canonical_uri}\n"
|
|
397
|
+
f"{canonical_query}\n"
|
|
398
|
+
f"{canonical_headers}\n"
|
|
399
|
+
f"{signed_headers}\n"
|
|
400
|
+
f"UNSIGNED-PAYLOAD"
|
|
401
|
+
)
|
|
402
|
+
|
|
403
|
+
string_to_sign = (
|
|
404
|
+
f"{_SIGV4_ALGORITHM}\n"
|
|
405
|
+
f"{amz_date}\n"
|
|
406
|
+
f"{credential_scope}\n"
|
|
407
|
+
f"{hashlib.sha256(canonical_request.encode('utf-8')).hexdigest()}"
|
|
408
|
+
)
|
|
409
|
+
signing_key = _derive_signing_key(
|
|
410
|
+
ctx["secret_access_key"], date_stamp, region, service
|
|
411
|
+
)
|
|
412
|
+
signature = hmac.new(
|
|
413
|
+
signing_key, string_to_sign.encode("utf-8"), hashlib.sha256
|
|
414
|
+
).hexdigest()
|
|
415
|
+
|
|
416
|
+
# X-Amz-Signature is hex (no encoding needed) and is NOT part of the
|
|
417
|
+
# canonical query. The sorted canonical query doubles as the URL query.
|
|
418
|
+
final_query = f"{canonical_query}&X-Amz-Signature={signature}"
|
|
419
|
+
|
|
420
|
+
url = request.url
|
|
421
|
+
fragment = url.hash or ""
|
|
422
|
+
presigned_href = zapros.URL(
|
|
423
|
+
f"{url.protocol}//{url.host}{url.pathname}?{final_query}{fragment}"
|
|
424
|
+
)
|
|
425
|
+
|
|
426
|
+
if request.body is not None:
|
|
427
|
+
return Request(
|
|
428
|
+
presigned_href,
|
|
429
|
+
request.method,
|
|
430
|
+
headers,
|
|
431
|
+
body=request.body,
|
|
432
|
+
context=request.context,
|
|
433
|
+
)
|
|
434
|
+
return Request(presigned_href, request.method, headers, context=request.context)
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
from typing import Optional, cast
|
|
2
|
+
|
|
3
|
+
from zapros import (
|
|
4
|
+
AsyncBaseHandler,
|
|
5
|
+
AsyncBaseMiddleware,
|
|
6
|
+
AsyncSyncMismatchError,
|
|
7
|
+
BaseHandler,
|
|
8
|
+
BaseMiddleware,
|
|
9
|
+
Request,
|
|
10
|
+
)
|
|
11
|
+
from zapros._models import Response
|
|
12
|
+
|
|
13
|
+
from ._signers import Signer
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def ensure_async_handler(
|
|
17
|
+
handler: AsyncBaseHandler | BaseHandler,
|
|
18
|
+
) -> AsyncBaseHandler:
|
|
19
|
+
if isinstance(handler, AsyncBaseHandler):
|
|
20
|
+
return handler
|
|
21
|
+
raise AsyncSyncMismatchError(
|
|
22
|
+
"Handler was expected to be an AsyncBaseHandler, but it is not."
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def ensure_sync_handler(
|
|
27
|
+
handler: AsyncBaseHandler | BaseHandler,
|
|
28
|
+
) -> BaseHandler:
|
|
29
|
+
if isinstance(handler, BaseHandler):
|
|
30
|
+
return handler
|
|
31
|
+
raise AsyncSyncMismatchError(
|
|
32
|
+
"Handler was expected to be a BaseHandler, but it is not."
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def strip_accept_encoding(request: Request) -> Request:
|
|
37
|
+
"""Drop the ``Accept-Encoding`` zapros adds to every request.
|
|
38
|
+
|
|
39
|
+
Like the AWS SDKs, the client never asks for transport compression: a
|
|
40
|
+
response body must be the service's actual bytes so ``Content-Length``,
|
|
41
|
+
checksums and ``Range`` stay coherent and a ``Content-Encoding`` on it is
|
|
42
|
+
always stored metadata (an S3 object's), never something to undo. Removing
|
|
43
|
+
the header before signing also keeps it out of the canonical request, where
|
|
44
|
+
a proxy rewriting it in flight would break the signature.
|
|
45
|
+
"""
|
|
46
|
+
if "accept-encoding" in request.headers:
|
|
47
|
+
del request.headers["Accept-Encoding"]
|
|
48
|
+
return request
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class AuthMiddleware(BaseMiddleware, AsyncBaseMiddleware):
|
|
52
|
+
"""Sign the outgoing request using the Signer placed in ``context["signer"]``.
|
|
53
|
+
|
|
54
|
+
The generated ``get_signer`` in each operation module resolves the
|
|
55
|
+
effective auth scheme to a concrete :class:`Signer` and threads it
|
|
56
|
+
through ``request.context`` before dispatch. This middleware does the
|
|
57
|
+
actual signing; ``None`` means the operation opted out via
|
|
58
|
+
``@optionalAuth`` and the request passes through unsigned. Either way
|
|
59
|
+
``Accept-Encoding`` is stripped first (see :func:`strip_accept_encoding`).
|
|
60
|
+
"""
|
|
61
|
+
|
|
62
|
+
def __init__(self, next_handler: BaseHandler | AsyncBaseHandler) -> None:
|
|
63
|
+
self.next = cast(BaseHandler, next_handler)
|
|
64
|
+
self.async_next = cast(AsyncBaseHandler, next_handler)
|
|
65
|
+
|
|
66
|
+
def handle(self, request: Request) -> Response:
|
|
67
|
+
next_handler = ensure_sync_handler(self.next)
|
|
68
|
+
request = strip_accept_encoding(request)
|
|
69
|
+
signer = cast(Optional[Signer], request.context.get("signer"))
|
|
70
|
+
if signer is not None:
|
|
71
|
+
request = signer.sign(request)
|
|
72
|
+
return next_handler.handle(request)
|
|
73
|
+
|
|
74
|
+
async def ahandle(self, request: Request) -> Response:
|
|
75
|
+
next_handler = ensure_async_handler(self.async_next)
|
|
76
|
+
request = strip_accept_encoding(request)
|
|
77
|
+
signer = cast(Optional[Signer], request.context.get("signer"))
|
|
78
|
+
if signer is not None:
|
|
79
|
+
request = await signer.asign(request)
|
|
80
|
+
return await next_handler.ahandle(request)
|