diff --git a/IMPLEMENTATION_COVERAGE.md b/IMPLEMENTATION_COVERAGE.md index cd1d4d482..a863d483d 100644 --- a/IMPLEMENTATION_COVERAGE.md +++ b/IMPLEMENTATION_COVERAGE.md @@ -1,4 +1,25 @@ +## accessanalyzer +0% implemented +- [ ] create_analyzer +- [ ] create_archive_rule +- [ ] delete_analyzer +- [ ] delete_archive_rule +- [ ] get_analyzed_resource +- [ ] get_analyzer +- [ ] get_archive_rule +- [ ] get_finding +- [ ] list_analyzed_resources +- [ ] list_analyzers +- [ ] list_archive_rules +- [ ] list_findings +- [ ] list_tags_for_resource +- [ ] start_resource_scan +- [ ] tag_resource +- [ ] untag_resource +- [ ] update_archive_rule +- [ ] update_findings + ## acm 38% implemented - [X] add_tags_to_certificate @@ -137,11 +158,13 @@ ## amplify 0% implemented - [ ] create_app +- [ ] create_backend_environment - [ ] create_branch - [ ] create_deployment - [ ] create_domain_association - [ ] create_webhook - [ ] delete_app +- [ ] delete_backend_environment - [ ] delete_branch - [ ] delete_domain_association - [ ] delete_job @@ -149,12 +172,14 @@ - [ ] generate_access_logs - [ ] get_app - [ ] get_artifact_url +- [ ] get_backend_environment - [ ] get_branch - [ ] get_domain_association - [ ] get_job - [ ] get_webhook - [ ] list_apps - [ ] list_artifacts +- [ ] list_backend_environments - [ ] list_branches - [ ] list_domain_associations - [ ] list_jobs @@ -315,6 +340,7 @@ - [ ] delete_api - [ ] delete_api_mapping - [ ] delete_authorizer +- [ ] delete_cors_configuration - [ ] delete_deployment - [ ] delete_domain_name - [ ] delete_integration @@ -322,6 +348,7 @@ - [ ] delete_model - [ ] delete_route - [ ] delete_route_response +- [ ] delete_route_settings - [ ] delete_stage - [ ] get_api - [ ] get_api_mapping @@ -347,6 +374,8 @@ - [ ] get_stage - [ ] get_stages - [ ] get_tags +- [ ] import_api +- [ ] reimport_api - [ ] tag_resource - [ ] untag_resource - [ ] update_api @@ -361,6 +390,38 @@ - [ ] update_route_response - [ ] update_stage +## appconfig +0% implemented +- [ ] create_application +- [ ] create_configuration_profile +- [ ] create_deployment_strategy +- [ ] create_environment +- [ ] delete_application +- [ ] delete_configuration_profile +- [ ] delete_deployment_strategy +- [ ] delete_environment +- [ ] get_application +- [ ] get_configuration +- [ ] get_configuration_profile +- [ ] get_deployment +- [ ] get_deployment_strategy +- [ ] get_environment +- [ ] list_applications +- [ ] list_configuration_profiles +- [ ] list_deployment_strategies +- [ ] list_deployments +- [ ] list_environments +- [ ] list_tags_for_resource +- [ ] start_deployment +- [ ] stop_deployment +- [ ] tag_resource +- [ ] untag_resource +- [ ] update_application +- [ ] update_configuration_profile +- [ ] update_deployment_strategy +- [ ] update_environment +- [ ] validate_configuration + ## application-autoscaling 0% implemented - [ ] delete_scaling_policy @@ -378,21 +439,30 @@ 0% implemented - [ ] create_application - [ ] create_component +- [ ] create_log_pattern - [ ] delete_application - [ ] delete_component +- [ ] delete_log_pattern - [ ] describe_application - [ ] describe_component - [ ] describe_component_configuration - [ ] describe_component_configuration_recommendation +- [ ] describe_log_pattern - [ ] describe_observation - [ ] describe_problem - [ ] describe_problem_observations - [ ] list_applications - [ ] list_components +- [ ] list_log_pattern_sets +- [ ] list_log_patterns - [ ] list_problems +- [ ] list_tags_for_resource +- [ ] tag_resource +- [ ] untag_resource - [ ] update_application - [ ] update_component - [ ] update_component_configuration +- [ ] update_log_pattern ## appmesh 0% implemented @@ -477,18 +547,22 @@ ## appsync 0% implemented +- [ ] create_api_cache - [ ] create_api_key - [ ] create_data_source - [ ] create_function - [ ] create_graphql_api - [ ] create_resolver - [ ] create_type +- [ ] delete_api_cache - [ ] delete_api_key - [ ] delete_data_source - [ ] delete_function - [ ] delete_graphql_api - [ ] delete_resolver - [ ] delete_type +- [ ] flush_api_cache +- [ ] get_api_cache - [ ] get_data_source - [ ] get_function - [ ] get_graphql_api @@ -507,6 +581,7 @@ - [ ] start_schema_creation - [ ] tag_resource - [ ] untag_resource +- [ ] update_api_cache - [ ] update_api_key - [ ] update_data_source - [ ] update_function @@ -686,6 +761,9 @@ ## ce 0% implemented +- [ ] create_cost_category_definition +- [ ] delete_cost_category_definition +- [ ] describe_cost_category_definition - [ ] get_cost_and_usage - [ ] get_cost_and_usage_with_resources - [ ] get_cost_forecast @@ -700,12 +778,15 @@ - [ ] get_savings_plans_utilization_details - [ ] get_tags - [ ] get_usage_forecast +- [ ] list_cost_category_definitions +- [ ] update_cost_category_definition ## chime 0% implemented - [ ] associate_phone_number_with_user - [ ] associate_phone_numbers_with_voice_connector - [ ] associate_phone_numbers_with_voice_connector_group +- [ ] batch_create_attendee - [ ] batch_create_room_membership - [ ] batch_delete_phone_number - [ ] batch_suspend_user @@ -713,14 +794,18 @@ - [ ] batch_update_phone_number - [ ] batch_update_user - [ ] create_account +- [ ] create_attendee - [ ] create_bot +- [ ] create_meeting - [ ] create_phone_number_order - [ ] create_room - [ ] create_room_membership - [ ] create_voice_connector - [ ] create_voice_connector_group - [ ] delete_account +- [ ] delete_attendee - [ ] delete_events_configuration +- [ ] delete_meeting - [ ] delete_phone_number - [ ] delete_room - [ ] delete_room_membership @@ -735,9 +820,11 @@ - [ ] disassociate_phone_numbers_from_voice_connector_group - [ ] get_account - [ ] get_account_settings +- [ ] get_attendee - [ ] get_bot - [ ] get_events_configuration - [ ] get_global_settings +- [ ] get_meeting - [ ] get_phone_number - [ ] get_phone_number_order - [ ] get_phone_number_settings @@ -753,7 +840,9 @@ - [ ] get_voice_connector_termination_health - [ ] invite_users - [ ] list_accounts +- [ ] list_attendees - [ ] list_bots +- [ ] list_meetings - [ ] list_phone_number_orders - [ ] list_phone_numbers - [ ] list_room_memberships @@ -869,7 +958,7 @@ - [ ] upgrade_published_schema ## cloudformation -40% implemented +32% implemented - [ ] cancel_update_stack - [ ] continue_update_rollback - [X] create_change_set @@ -880,6 +969,7 @@ - [X] delete_stack - [X] delete_stack_instances - [X] delete_stack_set +- [ ] deregister_type - [ ] describe_account_limits - [X] describe_change_set - [ ] describe_stack_drift_detection_status @@ -891,8 +981,11 @@ - [ ] describe_stack_set - [ ] describe_stack_set_operation - [X] describe_stacks +- [ ] describe_type +- [ ] describe_type_registration - [ ] detect_stack_drift - [ ] detect_stack_resource_drift +- [ ] detect_stack_set_drift - [ ] estimate_template_cost - [X] execute_change_set - [ ] get_stack_policy @@ -907,7 +1000,13 @@ - [ ] list_stack_set_operations - [ ] list_stack_sets - [X] list_stacks +- [ ] list_type_registrations +- [ ] list_type_versions +- [ ] list_types +- [ ] record_handler_progress +- [ ] register_type - [ ] set_stack_policy +- [ ] set_type_default_version - [ ] signal_resource - [ ] stop_stack_set_operation - [X] update_stack @@ -1045,6 +1144,7 @@ - [ ] delete_trail - [ ] describe_trails - [ ] get_event_selectors +- [ ] get_insight_selectors - [ ] get_trail - [ ] get_trail_status - [ ] list_public_keys @@ -1052,31 +1152,38 @@ - [ ] list_trails - [ ] lookup_events - [ ] put_event_selectors +- [ ] put_insight_selectors - [ ] remove_tags - [ ] start_logging - [ ] stop_logging - [ ] update_trail ## cloudwatch -39% implemented +34% implemented - [X] delete_alarms - [ ] delete_anomaly_detector - [X] delete_dashboards +- [ ] delete_insight_rules - [ ] describe_alarm_history - [ ] describe_alarms - [ ] describe_alarms_for_metric - [ ] describe_anomaly_detectors +- [ ] describe_insight_rules - [ ] disable_alarm_actions +- [ ] disable_insight_rules - [ ] enable_alarm_actions +- [ ] enable_insight_rules - [X] get_dashboard +- [ ] get_insight_rule_report - [ ] get_metric_data - [X] get_metric_statistics - [ ] get_metric_widget_image - [X] list_dashboards -- [ ] list_metrics +- [X] list_metrics - [ ] list_tags_for_resource - [ ] put_anomaly_detector - [X] put_dashboard +- [ ] put_insight_rule - [X] put_metric_alarm - [X] put_metric_data - [X] set_alarm_state @@ -1088,39 +1195,64 @@ - [ ] batch_delete_builds - [ ] batch_get_builds - [ ] batch_get_projects +- [ ] batch_get_report_groups +- [ ] batch_get_reports - [ ] create_project +- [ ] create_report_group - [ ] create_webhook - [ ] delete_project +- [ ] delete_report +- [ ] delete_report_group +- [ ] delete_resource_policy - [ ] delete_source_credentials - [ ] delete_webhook +- [ ] describe_test_cases +- [ ] get_resource_policy - [ ] import_source_credentials - [ ] invalidate_project_cache - [ ] list_builds - [ ] list_builds_for_project - [ ] list_curated_environment_images - [ ] list_projects +- [ ] list_report_groups +- [ ] list_reports +- [ ] list_reports_for_report_group +- [ ] list_shared_projects +- [ ] list_shared_report_groups - [ ] list_source_credentials +- [ ] put_resource_policy - [ ] start_build - [ ] stop_build - [ ] update_project +- [ ] update_report_group - [ ] update_webhook ## codecommit 0% implemented +- [ ] associate_approval_rule_template_with_repository +- [ ] batch_associate_approval_rule_template_with_repositories - [ ] batch_describe_merge_conflicts +- [ ] batch_disassociate_approval_rule_template_from_repositories - [ ] batch_get_commits - [ ] batch_get_repositories +- [ ] create_approval_rule_template - [ ] create_branch - [ ] create_commit - [ ] create_pull_request -- [ ] create_repository +- [ ] create_pull_request_approval_rule +- [X] create_repository - [ ] create_unreferenced_merge_commit +- [ ] delete_approval_rule_template - [ ] delete_branch - [ ] delete_comment_content - [ ] delete_file -- [ ] delete_repository +- [ ] delete_pull_request_approval_rule +- [X] delete_repository - [ ] describe_merge_conflicts - [ ] describe_pull_request_events +- [ ] disassociate_approval_rule_template_from_repository +- [ ] evaluate_pull_request_approval_rules +- [ ] get_approval_rule_template - [ ] get_blob - [ ] get_branch - [ ] get_comment @@ -1134,11 +1266,16 @@ - [ ] get_merge_conflicts - [ ] get_merge_options - [ ] get_pull_request -- [ ] get_repository +- [ ] get_pull_request_approval_states +- [ ] get_pull_request_override_state +- [X] get_repository - [ ] get_repository_triggers +- [ ] list_approval_rule_templates +- [ ] list_associated_approval_rule_templates_for_repository - [ ] list_branches - [ ] list_pull_requests - [ ] list_repositories +- [ ] list_repositories_for_approval_rule_template - [ ] list_tags_for_resource - [ ] merge_branches_by_fast_forward - [ ] merge_branches_by_squash @@ -1146,6 +1283,7 @@ - [ ] merge_pull_request_by_fast_forward - [ ] merge_pull_request_by_squash - [ ] merge_pull_request_by_three_way +- [ ] override_pull_request_approval_rules - [ ] post_comment_for_compared_commit - [ ] post_comment_for_pull_request - [ ] post_comment_reply @@ -1154,8 +1292,13 @@ - [ ] tag_resource - [ ] test_repository_triggers - [ ] untag_resource +- [ ] update_approval_rule_template_content +- [ ] update_approval_rule_template_description +- [ ] update_approval_rule_template_name - [ ] update_comment - [ ] update_default_branch +- [ ] update_pull_request_approval_rule_content +- [ ] update_pull_request_approval_state - [ ] update_pull_request_description - [ ] update_pull_request_status - [ ] update_pull_request_title @@ -1211,28 +1354,47 @@ - [ ] update_application - [ ] update_deployment_group -## codepipeline +## codeguru-reviewer 0% implemented +- [ ] associate_repository +- [ ] describe_repository_association +- [ ] disassociate_repository +- [ ] list_repository_associations + +## codeguruprofiler +0% implemented +- [ ] configure_agent +- [ ] create_profiling_group +- [ ] delete_profiling_group +- [ ] describe_profiling_group +- [ ] get_profile +- [ ] list_profile_times +- [ ] list_profiling_groups +- [ ] post_agent_profile +- [ ] update_profiling_group + +## codepipeline +22% implemented - [ ] acknowledge_job - [ ] acknowledge_third_party_job - [ ] create_custom_action_type -- [ ] create_pipeline +- [X] create_pipeline - [ ] delete_custom_action_type -- [ ] delete_pipeline +- [X] delete_pipeline - [ ] delete_webhook - [ ] deregister_webhook_with_third_party - [ ] disable_stage_transition - [ ] enable_stage_transition - [ ] get_job_details -- [ ] get_pipeline +- [X] get_pipeline - [ ] get_pipeline_execution - [ ] get_pipeline_state - [ ] get_third_party_job_details - [ ] list_action_executions - [ ] list_action_types - [ ] list_pipeline_executions -- [ ] list_pipelines -- [ ] list_tags_for_resource +- [X] list_pipelines +- [X] list_tags_for_resource - [ ] list_webhooks - [ ] poll_for_jobs - [ ] poll_for_third_party_jobs @@ -1246,9 +1408,9 @@ - [ ] register_webhook_with_third_party - [ ] retry_stage_execution - [ ] start_pipeline_execution -- [ ] tag_resource -- [ ] untag_resource -- [ ] update_pipeline +- [X] tag_resource +- [X] untag_resource +- [X] update_pipeline ## codestar 0% implemented @@ -1441,13 +1603,17 @@ - [ ] batch_detect_key_phrases - [ ] batch_detect_sentiment - [ ] batch_detect_syntax +- [ ] classify_document - [ ] create_document_classifier +- [ ] create_endpoint - [ ] create_entity_recognizer - [ ] delete_document_classifier +- [ ] delete_endpoint - [ ] delete_entity_recognizer - [ ] describe_document_classification_job - [ ] describe_document_classifier - [ ] describe_dominant_language_detection_job +- [ ] describe_endpoint - [ ] describe_entities_detection_job - [ ] describe_entity_recognizer - [ ] describe_key_phrases_detection_job @@ -1461,6 +1627,7 @@ - [ ] list_document_classification_jobs - [ ] list_document_classifiers - [ ] list_dominant_language_detection_jobs +- [ ] list_endpoints - [ ] list_entities_detection_jobs - [ ] list_entity_recognizers - [ ] list_key_phrases_detection_jobs @@ -1481,6 +1648,7 @@ - [ ] stop_training_entity_recognizer - [ ] tag_resource - [ ] untag_resource +- [ ] update_endpoint ## comprehendmedical 0% implemented @@ -1496,20 +1664,32 @@ - [ ] stop_entities_detection_v2_job - [ ] stop_phi_detection_job +## compute-optimizer +0% implemented +- [ ] get_auto_scaling_group_recommendations +- [ ] get_ec2_instance_recommendations +- [ ] get_ec2_recommendation_projected_metrics +- [ ] get_enrollment_status +- [ ] get_recommendation_summaries +- [ ] update_enrollment_status + ## config -31% implemented +25% implemented - [X] batch_get_aggregate_resource_config - [X] batch_get_resource_config - [X] delete_aggregation_authorization - [ ] delete_config_rule - [X] delete_configuration_aggregator - [X] delete_configuration_recorder +- [ ] delete_conformance_pack - [X] delete_delivery_channel - [ ] delete_evaluation_results - [ ] delete_organization_config_rule +- [ ] delete_organization_conformance_pack - [ ] delete_pending_aggregation_request - [ ] delete_remediation_configuration - [ ] delete_remediation_exceptions +- [ ] delete_resource_config - [ ] delete_retention_configuration - [ ] deliver_config_snapshot - [ ] describe_aggregate_compliance_by_config_rules @@ -1522,10 +1702,15 @@ - [X] describe_configuration_aggregators - [X] describe_configuration_recorder_status - [X] describe_configuration_recorders +- [ ] describe_conformance_pack_compliance +- [ ] describe_conformance_pack_status +- [ ] describe_conformance_packs - [ ] describe_delivery_channel_status - [X] describe_delivery_channels - [ ] describe_organization_config_rule_statuses - [ ] describe_organization_config_rules +- [ ] describe_organization_conformance_pack_statuses +- [ ] describe_organization_conformance_packs - [ ] describe_pending_aggregation_requests - [ ] describe_remediation_configurations - [ ] describe_remediation_exceptions @@ -1539,8 +1724,11 @@ - [ ] get_compliance_details_by_resource - [ ] get_compliance_summary_by_config_rule - [ ] get_compliance_summary_by_resource_type +- [ ] get_conformance_pack_compliance_details +- [ ] get_conformance_pack_compliance_summary - [ ] get_discovered_resource_counts - [ ] get_organization_config_rule_detailed_status +- [ ] get_organization_conformance_pack_detailed_status - [X] get_resource_config_history - [X] list_aggregate_discovered_resources - [X] list_discovered_resources @@ -1549,11 +1737,14 @@ - [ ] put_config_rule - [X] put_configuration_aggregator - [X] put_configuration_recorder +- [ ] put_conformance_pack - [X] put_delivery_channel - [ ] put_evaluations - [ ] put_organization_config_rule +- [ ] put_organization_conformance_pack - [ ] put_remediation_configurations - [ ] put_remediation_exceptions +- [ ] put_resource_config - [ ] put_retention_configuration - [ ] select_resource_config - [ ] start_config_rules_evaluation @@ -1583,6 +1774,7 @@ - [ ] list_tags_for_resource - [ ] list_user_hierarchy_groups - [ ] list_users +- [ ] start_chat_contact - [ ] start_outbound_voice_contact - [ ] stop_contact - [ ] tag_resource @@ -1594,6 +1786,14 @@ - [ ] update_user_routing_profile - [ ] update_user_security_profiles +## connectparticipant +0% implemented +- [ ] create_participant_connection +- [ ] disconnect_participant +- [ ] get_transcript +- [ ] send_event +- [ ] send_message + ## cur 0% implemented - [ ] delete_report_definition @@ -1702,6 +1902,20 @@ - [ ] update_parameter_group - [ ] update_subnet_group +## detective +0% implemented +- [ ] accept_invitation +- [ ] create_graph +- [ ] create_members +- [ ] delete_graph +- [ ] delete_members +- [ ] disassociate_membership +- [ ] get_members +- [ ] list_graphs +- [ ] list_invitations +- [ ] list_members +- [ ] reject_invitation + ## devicefarm 0% implemented - [ ] create_device_pool @@ -1982,24 +2196,31 @@ - [ ] delete_log_subscription - [ ] delete_snapshot - [ ] delete_trust +- [ ] deregister_certificate - [ ] deregister_event_topic +- [ ] describe_certificate - [ ] describe_conditional_forwarders - [ ] describe_directories - [ ] describe_domain_controllers - [ ] describe_event_topics +- [ ] describe_ldaps_settings - [ ] describe_shared_directories - [ ] describe_snapshots - [ ] describe_trusts +- [ ] disable_ldaps - [ ] disable_radius - [ ] disable_sso +- [ ] enable_ldaps - [ ] enable_radius - [ ] enable_sso - [ ] get_directory_limits - [ ] get_snapshot_limits +- [ ] list_certificates - [ ] list_ip_routes - [ ] list_log_subscriptions - [ ] list_schema_extensions - [ ] list_tags_for_resource +- [ ] register_certificate - [ ] register_event_topic - [ ] reject_shared_directory - [ ] remove_ip_routes @@ -2016,7 +2237,7 @@ - [ ] verify_trust ## dynamodb -19% implemented +17% implemented - [ ] batch_get_item - [ ] batch_write_item - [ ] create_backup @@ -2027,14 +2248,17 @@ - [X] delete_table - [ ] describe_backup - [ ] describe_continuous_backups +- [ ] describe_contributor_insights - [ ] describe_endpoints - [ ] describe_global_table - [ ] describe_global_table_settings - [ ] describe_limits - [ ] describe_table +- [ ] describe_table_replica_auto_scaling - [ ] describe_time_to_live - [X] get_item - [ ] list_backups +- [ ] list_contributor_insights - [ ] list_global_tables - [ ] list_tables - [ ] list_tags_of_resource @@ -2048,10 +2272,12 @@ - [ ] transact_write_items - [ ] untag_resource - [ ] update_continuous_backups +- [ ] update_contributor_insights - [ ] update_global_table - [ ] update_global_table_settings - [ ] update_item - [ ] update_table +- [ ] update_table_replica_auto_scaling - [ ] update_time_to_live ## dynamodbstreams @@ -2061,9 +2287,16 @@ - [X] get_shard_iterator - [X] list_streams +## ebs +0% implemented +- [ ] get_snapshot_block +- [ ] list_changed_blocks +- [ ] list_snapshot_blocks + ## ec2 -28% implemented +26% implemented - [ ] accept_reserved_instances_exchange_quote +- [ ] accept_transit_gateway_peering_attachment - [ ] accept_transit_gateway_vpc_attachment - [ ] accept_vpc_endpoint_connections - [X] accept_vpc_peering_connection @@ -2079,6 +2312,7 @@ - [ ] associate_iam_instance_profile - [X] associate_route_table - [ ] associate_subnet_cidr_block +- [ ] associate_transit_gateway_multicast_domain - [ ] associate_transit_gateway_route_table - [X] associate_vpc_cidr_block - [ ] attach_classic_link_vpc @@ -2119,6 +2353,8 @@ - [X] create_key_pair - [X] create_launch_template - [ ] create_launch_template_version +- [ ] create_local_gateway_route +- [ ] create_local_gateway_route_table_vpc_association - [X] create_nat_gateway - [X] create_network_acl - [X] create_network_acl_entry @@ -2139,6 +2375,8 @@ - [ ] create_traffic_mirror_session - [ ] create_traffic_mirror_target - [ ] create_transit_gateway +- [ ] create_transit_gateway_multicast_domain +- [ ] create_transit_gateway_peering_attachment - [ ] create_transit_gateway_route - [ ] create_transit_gateway_route_table - [ ] create_transit_gateway_vpc_attachment @@ -2163,6 +2401,8 @@ - [X] delete_key_pair - [ ] delete_launch_template - [ ] delete_launch_template_versions +- [ ] delete_local_gateway_route +- [ ] delete_local_gateway_route_table_vpc_association - [X] delete_nat_gateway - [X] delete_network_acl - [X] delete_network_acl_entry @@ -2182,6 +2422,8 @@ - [ ] delete_traffic_mirror_session - [ ] delete_traffic_mirror_target - [ ] delete_transit_gateway +- [ ] delete_transit_gateway_multicast_domain +- [ ] delete_transit_gateway_peering_attachment - [ ] delete_transit_gateway_route - [ ] delete_transit_gateway_route_table - [ ] delete_transit_gateway_vpc_attachment @@ -2196,6 +2438,8 @@ - [X] delete_vpn_gateway - [ ] deprovision_byoip_cidr - [X] deregister_image +- [ ] deregister_transit_gateway_multicast_group_members +- [ ] deregister_transit_gateway_multicast_group_sources - [ ] describe_account_attributes - [X] describe_addresses - [ ] describe_aggregate_id_format @@ -2209,6 +2453,7 @@ - [ ] describe_client_vpn_endpoints - [ ] describe_client_vpn_routes - [ ] describe_client_vpn_target_networks +- [ ] describe_coip_pools - [ ] describe_conversion_tasks - [ ] describe_customer_gateways - [X] describe_dhcp_options @@ -2216,6 +2461,7 @@ - [ ] describe_elastic_gpus - [ ] describe_export_image_tasks - [ ] describe_export_tasks +- [ ] describe_fast_snapshot_restores - [ ] describe_fleet_history - [ ] describe_fleet_instances - [ ] describe_fleets @@ -2235,11 +2481,19 @@ - [X] describe_instance_attribute - [ ] describe_instance_credit_specifications - [ ] describe_instance_status +- [ ] describe_instance_type_offerings +- [ ] describe_instance_types - [ ] describe_instances - [X] describe_internet_gateways - [X] describe_key_pairs - [ ] describe_launch_template_versions - [ ] describe_launch_templates +- [ ] describe_local_gateway_route_table_virtual_interface_group_associations +- [ ] describe_local_gateway_route_table_vpc_associations +- [ ] describe_local_gateway_route_tables +- [ ] describe_local_gateway_virtual_interface_groups +- [ ] describe_local_gateway_virtual_interfaces +- [ ] describe_local_gateways - [ ] describe_moving_addresses - [ ] describe_nat_gateways - [ ] describe_network_acls @@ -2275,6 +2529,8 @@ - [ ] describe_traffic_mirror_sessions - [ ] describe_traffic_mirror_targets - [ ] describe_transit_gateway_attachments +- [ ] describe_transit_gateway_multicast_domains +- [ ] describe_transit_gateway_peering_attachments - [ ] describe_transit_gateway_route_tables - [ ] describe_transit_gateway_vpc_attachments - [ ] describe_transit_gateways @@ -2283,8 +2539,8 @@ - [X] describe_volumes - [ ] describe_volumes_modifications - [X] describe_vpc_attribute -- [X] describe_vpc_classic_link -- [X] describe_vpc_classic_link_dns_support +- [ ] describe_vpc_classic_link +- [ ] describe_vpc_classic_link_dns_support - [ ] describe_vpc_endpoint_connection_notifications - [ ] describe_vpc_endpoint_connections - [ ] describe_vpc_endpoint_service_configurations @@ -2301,6 +2557,7 @@ - [X] detach_volume - [X] detach_vpn_gateway - [ ] disable_ebs_encryption_by_default +- [ ] disable_fast_snapshot_restores - [ ] disable_transit_gateway_route_table_propagation - [ ] disable_vgw_route_propagation - [X] disable_vpc_classic_link @@ -2310,9 +2567,11 @@ - [ ] disassociate_iam_instance_profile - [X] disassociate_route_table - [ ] disassociate_subnet_cidr_block +- [ ] disassociate_transit_gateway_multicast_domain - [ ] disassociate_transit_gateway_route_table - [X] disassociate_vpc_cidr_block - [ ] enable_ebs_encryption_by_default +- [ ] enable_fast_snapshot_restores - [ ] enable_transit_gateway_route_table_propagation - [ ] enable_vgw_route_propagation - [ ] enable_volume_io @@ -2323,8 +2582,10 @@ - [ ] export_image - [ ] export_transit_gateway_routes - [ ] get_capacity_reservation_usage +- [ ] get_coip_pool_usage - [ ] get_console_output - [ ] get_console_screenshot +- [ ] get_default_credit_specification - [ ] get_ebs_default_kms_key_id - [ ] get_ebs_encryption_by_default - [ ] get_host_reservation_purchase_preview @@ -2332,6 +2593,7 @@ - [ ] get_password_data - [ ] get_reserved_instances_exchange_quote - [ ] get_transit_gateway_attachment_propagations +- [ ] get_transit_gateway_multicast_domain_associations - [ ] get_transit_gateway_route_table_associations - [ ] get_transit_gateway_route_table_propagations - [ ] import_client_vpn_client_certificate_revocation_list @@ -2342,6 +2604,7 @@ - [ ] import_volume - [ ] modify_capacity_reservation - [ ] modify_client_vpn_endpoint +- [ ] modify_default_credit_specification - [ ] modify_ebs_default_kms_key_id - [ ] modify_fleet - [ ] modify_fpga_image_attribute @@ -2353,6 +2616,7 @@ - [ ] modify_instance_capacity_reservation_attributes - [ ] modify_instance_credit_specification - [ ] modify_instance_event_start_time +- [ ] modify_instance_metadata_options - [ ] modify_instance_placement - [ ] modify_launch_template - [X] modify_network_interface_attribute @@ -2384,6 +2648,9 @@ - [ ] purchase_scheduled_instances - [X] reboot_instances - [ ] register_image +- [ ] register_transit_gateway_multicast_group_members +- [ ] register_transit_gateway_multicast_group_sources +- [ ] reject_transit_gateway_peering_attachment - [ ] reject_transit_gateway_vpc_attachment - [ ] reject_vpc_endpoint_connections - [X] reject_vpc_peering_connection @@ -2410,6 +2677,8 @@ - [X] revoke_security_group_ingress - [ ] run_instances - [ ] run_scheduled_instances +- [ ] search_local_gateway_routes +- [ ] search_transit_gateway_multicast_groups - [ ] search_transit_gateway_routes - [ ] send_diagnostic_interrupt - [X] start_instances @@ -2425,7 +2694,7 @@ ## ec2-instance-connect 0% implemented -- [ ] send_ssh_public_key +- [x] send_ssh_public_key ## ecr 27% implemented @@ -2460,7 +2729,8 @@ - [ ] upload_layer_part ## ecs -66% implemented +62% implemented +- [ ] create_capacity_provider - [X] create_cluster - [X] create_service - [ ] create_task_set @@ -2471,6 +2741,7 @@ - [ ] delete_task_set - [X] deregister_container_instance - [X] deregister_task_definition +- [ ] describe_capacity_providers - [X] describe_clusters - [X] describe_container_instances - [X] describe_services @@ -2490,6 +2761,7 @@ - [ ] put_account_setting - [ ] put_account_setting_default - [X] put_attributes +- [ ] put_cluster_capacity_providers - [X] register_container_instance - [X] register_task_definition - [X] run_task @@ -2527,13 +2799,17 @@ ## eks 0% implemented - [ ] create_cluster +- [ ] create_fargate_profile - [ ] create_nodegroup - [ ] delete_cluster +- [ ] delete_fargate_profile - [ ] delete_nodegroup - [ ] describe_cluster +- [ ] describe_fargate_profile - [ ] describe_nodegroup - [ ] describe_update - [ ] list_clusters +- [ ] list_fargate_profiles - [ ] list_nodegroups - [ ] list_tags_for_resource - [ ] list_updates @@ -2544,6 +2820,12 @@ - [ ] update_nodegroup_config - [ ] update_nodegroup_version +## elastic-inference +0% implemented +- [ ] list_tags_for_resource +- [ ] tag_resource +- [ ] untag_resource + ## elasticache 0% implemented - [ ] add_tags_to_resource @@ -2732,7 +3014,7 @@ - [X] set_subnets ## emr -51% implemented +50% implemented - [ ] add_instance_fleet - [X] add_instance_groups - [X] add_job_flow_steps @@ -2752,6 +3034,7 @@ - [ ] list_instances - [ ] list_security_configurations - [X] list_steps +- [ ] modify_cluster - [ ] modify_instance_fleet - [X] modify_instance_groups - [ ] put_auto_scaling_policy @@ -2888,6 +3171,39 @@ 0% implemented - [ ] query_forecast +## frauddetector +0% implemented +- [ ] batch_create_variable +- [ ] batch_get_variable +- [ ] create_detector_version +- [ ] create_model_version +- [ ] create_rule +- [ ] create_variable +- [ ] delete_detector_version +- [ ] delete_event +- [ ] describe_detector +- [ ] describe_model_versions +- [ ] get_detector_version +- [ ] get_detectors +- [ ] get_external_models +- [ ] get_model_version +- [ ] get_models +- [ ] get_outcomes +- [ ] get_prediction +- [ ] get_rules +- [ ] get_variables +- [ ] put_detector +- [ ] put_external_model +- [ ] put_model +- [ ] put_outcome +- [ ] update_detector_version +- [ ] update_detector_version_metadata +- [ ] update_detector_version_status +- [ ] update_model_version +- [ ] update_rule_metadata +- [ ] update_rule_version +- [ ] update_variable + ## fsx 0% implemented - [ ] create_backup @@ -3344,7 +3660,7 @@ - [ ] describe_events ## iam -65% implemented +67% implemented - [ ] add_client_id_to_open_id_connect_provider - [X] add_role_to_instance_profile - [X] add_user_to_group @@ -3385,7 +3701,7 @@ - [ ] delete_service_linked_role - [ ] delete_service_specific_credential - [X] delete_signing_certificate -- [ ] delete_ssh_public_key +- [X] delete_ssh_public_key - [X] delete_user - [ ] delete_user_permissions_boundary - [X] delete_user_policy @@ -3419,7 +3735,7 @@ - [ ] get_service_last_accessed_details - [ ] get_service_last_accessed_details_with_entities - [ ] get_service_linked_role_deletion_status -- [ ] get_ssh_public_key +- [X] get_ssh_public_key - [X] get_user - [X] get_user_policy - [ ] list_access_keys @@ -3447,7 +3763,7 @@ - [X] list_signing_certificates - [ ] list_ssh_public_keys - [X] list_user_policies -- [ ] list_user_tags +- [X] list_user_tags - [X] list_users - [X] list_virtual_mfa_devices - [X] put_group_policy @@ -3480,11 +3796,56 @@ - [ ] update_server_certificate - [ ] update_service_specific_credential - [X] update_signing_certificate -- [ ] update_ssh_public_key +- [X] update_ssh_public_key - [X] update_user - [X] upload_server_certificate - [X] upload_signing_certificate -- [ ] upload_ssh_public_key +- [X] upload_ssh_public_key + +## imagebuilder +0% implemented +- [ ] cancel_image_creation +- [ ] create_component +- [ ] create_distribution_configuration +- [ ] create_image +- [ ] create_image_pipeline +- [ ] create_image_recipe +- [ ] create_infrastructure_configuration +- [ ] delete_component +- [ ] delete_distribution_configuration +- [ ] delete_image +- [ ] delete_image_pipeline +- [ ] delete_image_recipe +- [ ] delete_infrastructure_configuration +- [ ] get_component +- [ ] get_component_policy +- [ ] get_distribution_configuration +- [ ] get_image +- [ ] get_image_pipeline +- [ ] get_image_policy +- [ ] get_image_recipe +- [ ] get_image_recipe_policy +- [ ] get_infrastructure_configuration +- [ ] import_component +- [ ] list_component_build_versions +- [ ] list_components +- [ ] list_distribution_configurations +- [ ] list_image_build_versions +- [ ] list_image_pipeline_images +- [ ] list_image_pipelines +- [ ] list_image_recipes +- [ ] list_images +- [ ] list_infrastructure_configurations +- [ ] list_tags_for_resource +- [ ] put_component_policy +- [ ] put_image_policy +- [ ] put_image_recipe_policy +- [ ] start_image_pipeline_execution +- [ ] tag_resource +- [ ] untag_resource +- [ ] update_distribution_configuration +- [ ] update_image_pipeline +- [ ] update_infrastructure_configuration ## importexport 0% implemented @@ -3536,7 +3897,7 @@ - [ ] update_assessment_target ## iot -22% implemented +20% implemented - [ ] accept_certificate_transfer - [ ] add_thing_to_billing_group - [X] add_thing_to_thing_group @@ -3551,9 +3912,11 @@ - [ ] cancel_job - [ ] cancel_job_execution - [ ] clear_default_authorizer +- [ ] confirm_topic_rule_destination - [ ] create_authorizer - [ ] create_billing_group - [ ] create_certificate_from_csr +- [ ] create_domain_configuration - [ ] create_dynamic_thing_group - [X] create_job - [X] create_keys_and_certificate @@ -3561,6 +3924,9 @@ - [ ] create_ota_update - [X] create_policy - [ ] create_policy_version +- [ ] create_provisioning_claim +- [ ] create_provisioning_template +- [ ] create_provisioning_template_version - [ ] create_role_alias - [ ] create_scheduled_audit - [ ] create_security_profile @@ -3569,11 +3935,13 @@ - [X] create_thing_group - [X] create_thing_type - [ ] create_topic_rule +- [ ] create_topic_rule_destination - [ ] delete_account_audit_configuration - [ ] delete_authorizer - [ ] delete_billing_group - [ ] delete_ca_certificate - [X] delete_certificate +- [ ] delete_domain_configuration - [ ] delete_dynamic_thing_group - [ ] delete_job - [ ] delete_job_execution @@ -3581,6 +3949,8 @@ - [ ] delete_ota_update - [X] delete_policy - [ ] delete_policy_version +- [ ] delete_provisioning_template +- [ ] delete_provisioning_template_version - [ ] delete_registration_code - [ ] delete_role_alias - [ ] delete_scheduled_audit @@ -3590,6 +3960,7 @@ - [X] delete_thing_group - [X] delete_thing_type - [ ] delete_topic_rule +- [ ] delete_topic_rule_destination - [ ] delete_v2_logging_level - [ ] deprecate_thing_type - [ ] describe_account_audit_configuration @@ -3601,12 +3972,15 @@ - [ ] describe_ca_certificate - [X] describe_certificate - [ ] describe_default_authorizer +- [ ] describe_domain_configuration - [ ] describe_endpoint - [ ] describe_event_configurations - [ ] describe_index - [X] describe_job - [ ] describe_job_execution - [ ] describe_mitigation_action +- [ ] describe_provisioning_template +- [ ] describe_provisioning_template_version - [ ] describe_role_alias - [ ] describe_scheduled_audit - [ ] describe_security_profile @@ -3633,6 +4007,7 @@ - [ ] get_registration_code - [ ] get_statistics - [ ] get_topic_rule +- [ ] get_topic_rule_destination - [ ] get_v2_logging_options - [ ] list_active_violations - [ ] list_attached_policies @@ -3645,6 +4020,7 @@ - [ ] list_ca_certificates - [X] list_certificates - [ ] list_certificates_by_ca +- [ ] list_domain_configurations - [ ] list_indices - [ ] list_job_executions_for_job - [ ] list_job_executions_for_thing @@ -3657,6 +4033,8 @@ - [ ] list_policy_versions - [X] list_principal_policies - [X] list_principal_things +- [ ] list_provisioning_template_versions +- [ ] list_provisioning_templates - [ ] list_role_aliases - [ ] list_scheduled_audits - [ ] list_security_profiles @@ -3674,6 +4052,7 @@ - [X] list_things - [ ] list_things_in_billing_group - [X] list_things_in_thing_group +- [ ] list_topic_rule_destinations - [ ] list_topic_rules - [ ] list_v2_logging_levels - [ ] list_violation_events @@ -3704,11 +4083,13 @@ - [ ] update_billing_group - [ ] update_ca_certificate - [X] update_certificate +- [ ] update_domain_configuration - [ ] update_dynamic_thing_group - [ ] update_event_configurations - [ ] update_indexing_configuration - [ ] update_job - [ ] update_mitigation_action +- [ ] update_provisioning_template - [ ] update_role_alias - [ ] update_scheduled_audit - [ ] update_security_profile @@ -3716,6 +4097,7 @@ - [X] update_thing - [X] update_thing_group - [X] update_thing_groups_for_thing +- [ ] update_topic_rule_destination - [ ] validate_security_profile_behaviors ## iot-data @@ -3830,6 +4212,16 @@ - [ ] describe_detector - [ ] list_detectors +## iotsecuretunneling +0% implemented +- [ ] close_tunnel +- [ ] describe_tunnel +- [ ] list_tags_for_resource +- [ ] list_tunnels +- [ ] open_tunnel +- [ ] tag_resource +- [ ] untag_resource + ## iotthingsgraph 0% implemented - [ ] associate_entity_to_thing @@ -3889,6 +4281,30 @@ - [ ] update_broker_count - [ ] update_broker_storage - [ ] update_cluster_configuration +- [ ] update_monitoring + +## kendra +0% implemented +- [ ] batch_delete_document +- [ ] batch_put_document +- [ ] create_data_source +- [ ] create_faq +- [ ] create_index +- [ ] delete_faq +- [ ] delete_index +- [ ] describe_data_source +- [ ] describe_faq +- [ ] describe_index +- [ ] list_data_source_sync_jobs +- [ ] list_data_sources +- [ ] list_faqs +- [ ] list_indices +- [ ] query +- [ ] start_data_source_sync_job +- [ ] stop_data_source_sync_job +- [ ] submit_feedback +- [ ] update_data_source +- [ ] update_index ## kinesis 50% implemented @@ -3932,6 +4348,11 @@ 0% implemented - [ ] get_media +## kinesis-video-signaling +0% implemented +- [ ] get_ice_server_config +- [ ] send_alexa_offer_to_master + ## kinesisanalytics 0% implemented - [ ] add_application_cloud_watch_logging_option @@ -3962,6 +4383,7 @@ - [ ] add_application_input_processing_configuration - [ ] add_application_output - [ ] add_application_reference_data_source +- [ ] add_application_vpc_configuration - [ ] create_application - [ ] create_application_snapshot - [ ] delete_application @@ -3970,6 +4392,7 @@ - [ ] delete_application_output - [ ] delete_application_reference_data_source - [ ] delete_application_snapshot +- [ ] delete_application_vpc_configuration - [ ] describe_application - [ ] describe_application_snapshot - [ ] discover_input_schema @@ -3984,19 +4407,28 @@ ## kinesisvideo 0% implemented +- [ ] create_signaling_channel - [ ] create_stream +- [ ] delete_signaling_channel - [ ] delete_stream +- [ ] describe_signaling_channel - [ ] describe_stream - [ ] get_data_endpoint +- [ ] get_signaling_channel_endpoint +- [ ] list_signaling_channels - [ ] list_streams +- [ ] list_tags_for_resource - [ ] list_tags_for_stream +- [ ] tag_resource - [ ] tag_stream +- [ ] untag_resource - [ ] untag_stream - [ ] update_data_retention +- [ ] update_signaling_channel - [ ] update_stream ## kms -48% implemented +43% implemented - [X] cancel_key_deletion - [ ] connect_custom_key_store - [ ] create_alias @@ -4016,11 +4448,14 @@ - [X] enable_key_rotation - [X] encrypt - [X] generate_data_key +- [ ] generate_data_key_pair +- [ ] generate_data_key_pair_without_plaintext - [ ] generate_data_key_without_plaintext - [ ] generate_random - [X] get_key_policy - [X] get_key_rotation_status - [ ] get_parameters_for_import +- [ ] get_public_key - [ ] import_key_material - [ ] list_aliases - [ ] list_grants @@ -4033,11 +4468,13 @@ - [ ] retire_grant - [ ] revoke_grant - [X] schedule_key_deletion +- [ ] sign - [X] tag_resource - [ ] untag_resource - [ ] update_alias - [ ] update_custom_key_store - [X] update_key_description +- [ ] verify ## lakeformation 0% implemented @@ -4056,7 +4493,7 @@ - [ ] update_resource ## lambda -41% implemented +32% implemented - [ ] add_layer_version_permission - [ ] add_permission - [ ] create_alias @@ -4066,28 +4503,37 @@ - [X] delete_event_source_mapping - [X] delete_function - [ ] delete_function_concurrency +- [ ] delete_function_event_invoke_config - [ ] delete_layer_version +- [ ] delete_provisioned_concurrency_config - [ ] get_account_settings - [ ] get_alias - [X] get_event_source_mapping - [X] get_function +- [ ] get_function_concurrency - [ ] get_function_configuration +- [ ] get_function_event_invoke_config - [ ] get_layer_version - [ ] get_layer_version_by_arn - [ ] get_layer_version_policy - [ ] get_policy +- [ ] get_provisioned_concurrency_config - [X] invoke - [ ] invoke_async - [ ] list_aliases - [X] list_event_source_mappings +- [ ] list_function_event_invoke_configs - [X] list_functions - [ ] list_layer_versions - [ ] list_layers +- [ ] list_provisioned_concurrency_configs - [X] list_tags - [X] list_versions_by_function - [ ] publish_layer_version - [ ] publish_version - [ ] put_function_concurrency +- [ ] put_function_event_invoke_config +- [ ] put_provisioned_concurrency_config - [ ] remove_layer_version_permission - [ ] remove_permission - [X] tag_resource @@ -4096,6 +4542,7 @@ - [X] update_event_source_mapping - [X] update_function_code - [X] update_function_configuration +- [ ] update_function_event_invoke_config ## lex-models 0% implemented @@ -4151,6 +4598,7 @@ - [ ] get_license_configuration - [ ] get_service_settings - [ ] list_associations_for_license_configuration +- [ ] list_failures_for_license_configuration_operations - [ ] list_license_configurations - [ ] list_license_specifications_for_resource - [ ] list_resource_inventory @@ -4446,32 +4894,44 @@ - [ ] create_channel - [ ] create_input - [ ] create_input_security_group +- [ ] create_multiplex +- [ ] create_multiplex_program - [ ] create_tags - [ ] delete_channel - [ ] delete_input - [ ] delete_input_security_group +- [ ] delete_multiplex +- [ ] delete_multiplex_program - [ ] delete_reservation - [ ] delete_schedule - [ ] delete_tags - [ ] describe_channel - [ ] describe_input - [ ] describe_input_security_group +- [ ] describe_multiplex +- [ ] describe_multiplex_program - [ ] describe_offering - [ ] describe_reservation - [ ] describe_schedule - [ ] list_channels - [ ] list_input_security_groups - [ ] list_inputs +- [ ] list_multiplex_programs +- [ ] list_multiplexes - [ ] list_offerings - [ ] list_reservations - [ ] list_tags_for_resource - [ ] purchase_offering - [ ] start_channel +- [ ] start_multiplex - [ ] stop_channel +- [ ] stop_multiplex - [ ] update_channel - [ ] update_channel_class - [ ] update_input - [ ] update_input_security_group +- [ ] update_multiplex +- [ ] update_multiplex_program - [ ] update_reservation ## mediapackage @@ -4575,6 +5035,12 @@ - [ ] notify_migration_task_state - [ ] put_resource_attributes +## migrationhub-config +0% implemented +- [ ] create_home_region_control +- [ ] describe_home_region_controls +- [ ] get_home_region + ## mobile 0% implemented - [ ] create_project @@ -4714,6 +5180,37 @@ - [ ] restore_db_cluster_from_snapshot - [ ] restore_db_cluster_to_point_in_time +## networkmanager +0% implemented +- [ ] associate_customer_gateway +- [ ] associate_link +- [ ] create_device +- [ ] create_global_network +- [ ] create_link +- [ ] create_site +- [ ] delete_device +- [ ] delete_global_network +- [ ] delete_link +- [ ] delete_site +- [ ] deregister_transit_gateway +- [ ] describe_global_networks +- [ ] disassociate_customer_gateway +- [ ] disassociate_link +- [ ] get_customer_gateway_associations +- [ ] get_devices +- [ ] get_link_associations +- [ ] get_links +- [ ] get_sites +- [ ] get_transit_gateway_registrations +- [ ] list_tags_for_resource +- [ ] register_transit_gateway +- [ ] tag_resource +- [ ] untag_resource +- [ ] update_device +- [ ] update_global_network +- [ ] update_link +- [ ] update_site + ## opsworks 12% implemented - [ ] assign_instance @@ -4811,7 +5308,7 @@ - [ ] update_server_engine_attributes ## organizations -43% implemented +48% implemented - [ ] accept_handshake - [X] attach_policy - [ ] cancel_handshake @@ -4826,6 +5323,7 @@ - [ ] delete_policy - [X] describe_account - [X] describe_create_account_status +- [ ] describe_effective_policy - [ ] describe_handshake - [X] describe_organization - [X] describe_organizational_unit @@ -4850,15 +5348,23 @@ - [X] list_policies - [X] list_policies_for_target - [X] list_roots -- [x] list_tags_for_resource +- [X] list_tags_for_resource - [X] list_targets_for_policy - [X] move_account - [ ] remove_account_from_organization -- [x] tag_resource -- [x] untag_resource +- [X] tag_resource +- [X] untag_resource - [ ] update_organizational_unit - [ ] update_policy +## outposts +0% implemented +- [ ] create_outpost +- [ ] get_outpost +- [ ] get_outpost_instance_types +- [ ] list_outposts +- [ ] list_sites + ## personalize 0% implemented - [ ] create_batch_inference_job @@ -4926,6 +5432,7 @@ - [ ] create_push_template - [ ] create_segment - [ ] create_sms_template +- [ ] create_voice_template - [ ] delete_adm_channel - [ ] delete_apns_channel - [ ] delete_apns_sandbox_channel @@ -4946,6 +5453,7 @@ - [ ] delete_sms_template - [ ] delete_user_endpoints - [ ] delete_voice_channel +- [ ] delete_voice_template - [ ] get_adm_channel - [ ] get_apns_channel - [ ] get_apns_sandbox_channel @@ -4987,6 +5495,7 @@ - [ ] get_sms_template - [ ] get_user_endpoints - [ ] get_voice_channel +- [ ] get_voice_template - [ ] list_journeys - [ ] list_tags_for_resource - [ ] list_templates @@ -5018,6 +5527,7 @@ - [ ] update_sms_channel - [ ] update_sms_template - [ ] update_voice_channel +- [ ] update_voice_template ## pinpoint-email 0% implemented @@ -5116,38 +5626,93 @@ ## quicksight 0% implemented +- [ ] cancel_ingestion +- [ ] create_dashboard +- [ ] create_data_set +- [ ] create_data_source - [ ] create_group - [ ] create_group_membership +- [ ] create_iam_policy_assignment +- [ ] create_ingestion +- [ ] create_template +- [ ] create_template_alias +- [ ] delete_dashboard +- [ ] delete_data_set +- [ ] delete_data_source - [ ] delete_group - [ ] delete_group_membership +- [ ] delete_iam_policy_assignment +- [ ] delete_template +- [ ] delete_template_alias - [ ] delete_user - [ ] delete_user_by_principal_id +- [ ] describe_dashboard +- [ ] describe_dashboard_permissions +- [ ] describe_data_set +- [ ] describe_data_set_permissions +- [ ] describe_data_source +- [ ] describe_data_source_permissions - [ ] describe_group +- [ ] describe_iam_policy_assignment +- [ ] describe_ingestion +- [ ] describe_template +- [ ] describe_template_alias +- [ ] describe_template_permissions - [ ] describe_user - [ ] get_dashboard_embed_url +- [ ] list_dashboard_versions +- [ ] list_dashboards +- [ ] list_data_sets +- [ ] list_data_sources - [ ] list_group_memberships - [ ] list_groups +- [ ] list_iam_policy_assignments +- [ ] list_iam_policy_assignments_for_user +- [ ] list_ingestions +- [ ] list_tags_for_resource +- [ ] list_template_aliases +- [ ] list_template_versions +- [ ] list_templates - [ ] list_user_groups - [ ] list_users - [ ] register_user +- [ ] tag_resource +- [ ] untag_resource +- [ ] update_dashboard +- [ ] update_dashboard_permissions +- [ ] update_dashboard_published_version +- [ ] update_data_set +- [ ] update_data_set_permissions +- [ ] update_data_source +- [ ] update_data_source_permissions - [ ] update_group +- [ ] update_iam_policy_assignment +- [ ] update_template +- [ ] update_template_alias +- [ ] update_template_permissions - [ ] update_user ## ram 0% implemented - [ ] accept_resource_share_invitation - [ ] associate_resource_share +- [ ] associate_resource_share_permission - [ ] create_resource_share - [ ] delete_resource_share - [ ] disassociate_resource_share +- [ ] disassociate_resource_share_permission - [ ] enable_sharing_with_aws_organization +- [ ] get_permission - [ ] get_resource_policies - [ ] get_resource_share_associations - [ ] get_resource_share_invitations - [ ] get_resource_shares - [ ] list_pending_invitation_resources +- [ ] list_permissions - [ ] list_principals +- [ ] list_resource_share_permissions - [ ] list_resources +- [ ] promote_resource_share_created_from_policy - [ ] reject_resource_share_invitation - [ ] tag_resource - [ ] untag_resource @@ -5175,6 +5740,7 @@ - [ ] create_db_instance - [ ] create_db_instance_read_replica - [ ] create_db_parameter_group +- [ ] create_db_proxy - [ ] create_db_security_group - [ ] create_db_snapshot - [ ] create_db_subnet_group @@ -5189,6 +5755,7 @@ - [ ] delete_db_instance - [ ] delete_db_instance_automated_backup - [ ] delete_db_parameter_group +- [ ] delete_db_proxy - [ ] delete_db_security_group - [ ] delete_db_snapshot - [ ] delete_db_subnet_group @@ -5196,6 +5763,7 @@ - [ ] delete_global_cluster - [ ] delete_installation_media - [ ] delete_option_group +- [ ] deregister_db_proxy_targets - [ ] describe_account_attributes - [ ] describe_certificates - [ ] describe_custom_availability_zones @@ -5212,6 +5780,9 @@ - [ ] describe_db_log_files - [ ] describe_db_parameter_groups - [ ] describe_db_parameters +- [ ] describe_db_proxies +- [ ] describe_db_proxy_target_groups +- [ ] describe_db_proxy_targets - [ ] describe_db_security_groups - [ ] describe_db_snapshot_attributes - [ ] describe_db_snapshots @@ -5242,6 +5813,8 @@ - [ ] modify_db_cluster_snapshot_attribute - [ ] modify_db_instance - [ ] modify_db_parameter_group +- [ ] modify_db_proxy +- [ ] modify_db_proxy_target_group - [ ] modify_db_snapshot - [ ] modify_db_snapshot_attribute - [ ] modify_db_subnet_group @@ -5252,6 +5825,7 @@ - [ ] promote_read_replica_db_cluster - [ ] purchase_reserved_db_instances_offering - [ ] reboot_db_instance +- [ ] register_db_proxy_targets - [ ] remove_from_global_cluster - [ ] remove_role_from_db_cluster - [ ] remove_role_from_db_instance @@ -5283,7 +5857,7 @@ - [ ] rollback_transaction ## redshift -31% implemented +30% implemented - [ ] accept_reserved_node_exchange - [ ] authorize_cluster_security_group_ingress - [ ] authorize_snapshot_access @@ -5299,6 +5873,7 @@ - [ ] create_event_subscription - [ ] create_hsm_client_certificate - [ ] create_hsm_configuration +- [ ] create_scheduled_action - [X] create_snapshot_copy_grant - [ ] create_snapshot_schedule - [X] create_tags @@ -5310,6 +5885,7 @@ - [ ] delete_event_subscription - [ ] delete_hsm_client_certificate - [ ] delete_hsm_configuration +- [ ] delete_scheduled_action - [X] delete_snapshot_copy_grant - [ ] delete_snapshot_schedule - [X] delete_tags @@ -5335,6 +5911,7 @@ - [ ] describe_reserved_node_offerings - [ ] describe_reserved_nodes - [ ] describe_resize +- [ ] describe_scheduled_actions - [X] describe_snapshot_copy_grants - [ ] describe_snapshot_schedules - [ ] describe_storage @@ -5355,6 +5932,7 @@ - [ ] modify_cluster_snapshot_schedule - [ ] modify_cluster_subnet_group - [ ] modify_event_subscription +- [ ] modify_scheduled_action - [X] modify_snapshot_copy_retention_period - [ ] modify_snapshot_schedule - [ ] purchase_reserved_node_offering @@ -5371,12 +5949,17 @@ 0% implemented - [ ] compare_faces - [ ] create_collection +- [ ] create_project +- [ ] create_project_version - [ ] create_stream_processor - [ ] delete_collection - [ ] delete_faces - [ ] delete_stream_processor - [ ] describe_collection +- [ ] describe_project_versions +- [ ] describe_projects - [ ] describe_stream_processor +- [ ] detect_custom_labels - [ ] detect_faces - [ ] detect_labels - [ ] detect_moderation_labels @@ -5401,7 +5984,9 @@ - [ ] start_face_search - [ ] start_label_detection - [ ] start_person_tracking +- [ ] start_project_version - [ ] start_stream_processor +- [ ] stop_project_version - [ ] stop_stream_processor ## resource-groups @@ -5420,10 +6005,13 @@ - [X] update_group_query ## resourcegroupstaggingapi -60% implemented +37% implemented +- [ ] describe_report_creation +- [ ] get_compliance_summary - [X] get_resources - [X] get_tag_keys - [X] get_tag_values +- [ ] start_report_creation - [ ] tag_resources - [ ] untag_resources @@ -5589,63 +6177,63 @@ - [X] delete_bucket_cors - [ ] delete_bucket_encryption - [ ] delete_bucket_inventory_configuration -- [ ] delete_bucket_lifecycle +- [X] delete_bucket_lifecycle - [ ] delete_bucket_metrics_configuration - [X] delete_bucket_policy - [ ] delete_bucket_replication - [X] delete_bucket_tagging - [ ] delete_bucket_website -- [ ] delete_object +- [X] delete_object - [ ] delete_object_tagging -- [ ] delete_objects -- [ ] delete_public_access_block +- [X] delete_objects +- [X] delete_public_access_block - [ ] get_bucket_accelerate_configuration - [X] get_bucket_acl - [ ] get_bucket_analytics_configuration -- [ ] get_bucket_cors +- [X] get_bucket_cors - [ ] get_bucket_encryption - [ ] get_bucket_inventory_configuration -- [ ] get_bucket_lifecycle -- [ ] get_bucket_lifecycle_configuration -- [ ] get_bucket_location -- [ ] get_bucket_logging +- [X] get_bucket_lifecycle +- [X] get_bucket_lifecycle_configuration +- [X] get_bucket_location +- [X] get_bucket_logging - [ ] get_bucket_metrics_configuration - [ ] get_bucket_notification - [ ] get_bucket_notification_configuration - [X] get_bucket_policy -- [ ] get_bucket_policy_status +- [X] get_bucket_policy_status - [ ] get_bucket_replication - [ ] get_bucket_request_payment -- [ ] get_bucket_tagging +- [X] get_bucket_tagging - [X] get_bucket_versioning - [ ] get_bucket_website -- [ ] get_object -- [ ] get_object_acl +- [X] get_object +- [X] get_object_acl - [ ] get_object_legal_hold - [ ] get_object_lock_configuration - [ ] get_object_retention - [ ] get_object_tagging - [ ] get_object_torrent -- [ ] get_public_access_block +- [X] get_public_access_block - [ ] head_bucket - [ ] head_object - [ ] list_bucket_analytics_configurations - [ ] list_bucket_inventory_configurations - [ ] list_bucket_metrics_configurations -- [ ] list_buckets -- [ ] list_multipart_uploads +- [X] list_buckets +- [X] list_multipart_uploads - [ ] list_object_versions -- [ ] list_objects -- [ ] list_objects_v2 +- [X] list_objects +- [X] list_objects_v2 - [ ] list_parts - [X] put_bucket_accelerate_configuration -- [ ] put_bucket_acl +- [X] put_bucket_acl - [ ] put_bucket_analytics_configuration - [X] put_bucket_cors - [ ] put_bucket_encryption - [ ] put_bucket_inventory_configuration -- [ ] put_bucket_lifecycle -- [ ] put_bucket_lifecycle_configuration +- [X] put_bucket_lifecycle +- [X] put_bucket_lifecycle_configuration - [X] put_bucket_logging - [ ] put_bucket_metrics_configuration - [ ] put_bucket_notification @@ -5654,15 +6242,15 @@ - [ ] put_bucket_replication - [ ] put_bucket_request_payment - [X] put_bucket_tagging -- [ ] put_bucket_versioning +- [X] put_bucket_versioning - [ ] put_bucket_website -- [ ] put_object +- [X] put_object - [ ] put_object_acl - [ ] put_object_legal_hold - [ ] put_object_lock_configuration - [ ] put_object_retention - [ ] put_object_tagging -- [ ] put_public_access_block +- [X] put_public_access_block - [ ] restore_object - [ ] select_object_content - [ ] upload_part @@ -5670,11 +6258,19 @@ ## s3control 0% implemented +- [ ] create_access_point - [ ] create_job +- [ ] delete_access_point +- [ ] delete_access_point_policy - [ ] delete_public_access_block - [ ] describe_job +- [ ] get_access_point +- [ ] get_access_point_policy +- [ ] get_access_point_policy_status - [ ] get_public_access_block +- [ ] list_access_points - [ ] list_jobs +- [ ] put_access_point_policy - [ ] put_public_access_block - [ ] update_job_priority - [ ] update_job_status @@ -5682,81 +6278,145 @@ ## sagemaker 0% implemented - [ ] add_tags +- [ ] associate_trial_component - [ ] create_algorithm +- [ ] create_app +- [ ] create_auto_ml_job - [ ] create_code_repository - [ ] create_compilation_job +- [ ] create_domain - [ ] create_endpoint - [ ] create_endpoint_config +- [ ] create_experiment +- [ ] create_flow_definition +- [ ] create_human_task_ui - [ ] create_hyper_parameter_tuning_job - [ ] create_labeling_job - [ ] create_model - [ ] create_model_package +- [ ] create_monitoring_schedule - [ ] create_notebook_instance - [ ] create_notebook_instance_lifecycle_config +- [ ] create_presigned_domain_url - [ ] create_presigned_notebook_instance_url +- [ ] create_processing_job - [ ] create_training_job - [ ] create_transform_job +- [ ] create_trial +- [ ] create_trial_component +- [ ] create_user_profile - [ ] create_workteam - [ ] delete_algorithm +- [ ] delete_app - [ ] delete_code_repository +- [ ] delete_domain - [ ] delete_endpoint - [ ] delete_endpoint_config +- [ ] delete_experiment +- [ ] delete_flow_definition - [ ] delete_model - [ ] delete_model_package +- [ ] delete_monitoring_schedule - [ ] delete_notebook_instance - [ ] delete_notebook_instance_lifecycle_config - [ ] delete_tags +- [ ] delete_trial +- [ ] delete_trial_component +- [ ] delete_user_profile - [ ] delete_workteam - [ ] describe_algorithm +- [ ] describe_app +- [ ] describe_auto_ml_job - [ ] describe_code_repository - [ ] describe_compilation_job +- [ ] describe_domain - [ ] describe_endpoint - [ ] describe_endpoint_config +- [ ] describe_experiment +- [ ] describe_flow_definition +- [ ] describe_human_task_ui - [ ] describe_hyper_parameter_tuning_job - [ ] describe_labeling_job - [ ] describe_model - [ ] describe_model_package +- [ ] describe_monitoring_schedule - [ ] describe_notebook_instance - [ ] describe_notebook_instance_lifecycle_config +- [ ] describe_processing_job - [ ] describe_subscribed_workteam - [ ] describe_training_job - [ ] describe_transform_job +- [ ] describe_trial +- [ ] describe_trial_component +- [ ] describe_user_profile - [ ] describe_workteam +- [ ] disassociate_trial_component - [ ] get_search_suggestions - [ ] list_algorithms +- [ ] list_apps +- [ ] list_auto_ml_jobs +- [ ] list_candidates_for_auto_ml_job - [ ] list_code_repositories - [ ] list_compilation_jobs +- [ ] list_domains - [ ] list_endpoint_configs - [ ] list_endpoints +- [ ] list_experiments +- [ ] list_flow_definitions +- [ ] list_human_task_uis - [ ] list_hyper_parameter_tuning_jobs - [ ] list_labeling_jobs - [ ] list_labeling_jobs_for_workteam - [ ] list_model_packages - [ ] list_models +- [ ] list_monitoring_executions +- [ ] list_monitoring_schedules - [ ] list_notebook_instance_lifecycle_configs - [ ] list_notebook_instances +- [ ] list_processing_jobs - [ ] list_subscribed_workteams - [ ] list_tags - [ ] list_training_jobs - [ ] list_training_jobs_for_hyper_parameter_tuning_job - [ ] list_transform_jobs +- [ ] list_trial_components +- [ ] list_trials +- [ ] list_user_profiles - [ ] list_workteams - [ ] render_ui_template - [ ] search +- [ ] start_monitoring_schedule - [ ] start_notebook_instance +- [ ] stop_auto_ml_job - [ ] stop_compilation_job - [ ] stop_hyper_parameter_tuning_job - [ ] stop_labeling_job +- [ ] stop_monitoring_schedule - [ ] stop_notebook_instance +- [ ] stop_processing_job - [ ] stop_training_job - [ ] stop_transform_job - [ ] update_code_repository +- [ ] update_domain - [ ] update_endpoint - [ ] update_endpoint_weights_and_capacities +- [ ] update_experiment +- [ ] update_monitoring_schedule - [ ] update_notebook_instance - [ ] update_notebook_instance_lifecycle_config +- [ ] update_trial +- [ ] update_trial_component +- [ ] update_user_profile - [ ] update_workteam +## sagemaker-a2i-runtime +0% implemented +- [ ] delete_human_loop +- [ ] describe_human_loop +- [ ] list_human_loops +- [ ] start_human_loop +- [ ] stop_human_loop + ## sagemaker-runtime 0% implemented - [ ] invoke_endpoint @@ -5772,6 +6432,38 @@ - [ ] tag_resource - [ ] untag_resource +## schemas +0% implemented +- [ ] create_discoverer +- [ ] create_registry +- [ ] create_schema +- [ ] delete_discoverer +- [ ] delete_registry +- [ ] delete_schema +- [ ] delete_schema_version +- [ ] describe_code_binding +- [ ] describe_discoverer +- [ ] describe_registry +- [ ] describe_schema +- [ ] get_code_binding_source +- [ ] get_discovered_schema +- [ ] list_discoverers +- [ ] list_registries +- [ ] list_schema_versions +- [ ] list_schemas +- [ ] list_tags_for_resource +- [ ] lock_service_linked_role +- [ ] put_code_binding +- [ ] search_schemas +- [ ] start_discoverer +- [ ] stop_discoverer +- [ ] tag_resource +- [ ] unlock_service_linked_role +- [ ] untag_resource +- [ ] update_discoverer +- [ ] update_registry +- [ ] update_schema + ## sdb 0% implemented - [ ] batch_delete_attributes @@ -5786,14 +6478,14 @@ - [ ] select ## secretsmanager -55% implemented +61% implemented - [ ] cancel_rotate_secret - [X] create_secret - [ ] delete_resource_policy - [X] delete_secret - [X] describe_secret - [X] get_random_password -- [ ] get_resource_policy +- [X] get_resource_policy - [X] get_secret_value - [X] list_secret_version_ids - [X] list_secrets @@ -6076,6 +6768,7 @@ - [ ] delete_configuration_set_event_destination - [ ] delete_dedicated_ip_pool - [ ] delete_email_identity +- [ ] delete_suppressed_destination - [ ] get_account - [ ] get_blacklist_reports - [ ] get_configuration_set @@ -6087,24 +6780,30 @@ - [ ] get_domain_deliverability_campaign - [ ] get_domain_statistics_report - [ ] get_email_identity +- [ ] get_suppressed_destination - [ ] list_configuration_sets - [ ] list_dedicated_ip_pools - [ ] list_deliverability_test_reports - [ ] list_domain_deliverability_campaigns - [ ] list_email_identities +- [ ] list_suppressed_destinations - [ ] list_tags_for_resource - [ ] put_account_dedicated_ip_warmup_attributes - [ ] put_account_sending_attributes +- [ ] put_account_suppression_attributes - [ ] put_configuration_set_delivery_options - [ ] put_configuration_set_reputation_options - [ ] put_configuration_set_sending_options +- [ ] put_configuration_set_suppression_options - [ ] put_configuration_set_tracking_options - [ ] put_dedicated_ip_in_pool - [ ] put_dedicated_ip_warmup_attributes - [ ] put_deliverability_dashboard_option - [ ] put_email_identity_dkim_attributes +- [ ] put_email_identity_dkim_signing_attributes - [ ] put_email_identity_feedback_attributes - [ ] put_email_identity_mail_from_attributes +- [ ] put_suppressed_destination - [ ] send_email - [ ] tag_resource - [ ] untag_resource @@ -6328,6 +7027,7 @@ - [ ] describe_patch_properties - [ ] describe_sessions - [ ] get_automation_execution +- [ ] get_calendar_state - [X] get_command_invocation - [ ] get_connection_status - [ ] get_default_patch_baseline @@ -6390,6 +7090,7 @@ - [ ] update_managed_instance_role - [ ] update_ops_item - [ ] update_patch_baseline +- [ ] update_resource_data_sync - [ ] update_service_setting ## sso @@ -6457,6 +7158,7 @@ - [ ] delete_tape - [ ] delete_tape_archive - [ ] delete_volume +- [ ] describe_availability_monitor_test - [ ] describe_bandwidth_rate_limit - [ ] describe_cache - [ ] describe_cached_iscsi_volumes @@ -6494,6 +7196,7 @@ - [ ] set_local_console_password - [ ] set_smb_guest_password - [ ] shutdown_gateway +- [ ] start_availability_monitor_test - [ ] start_gateway - [ ] update_bandwidth_rate_limit - [ ] update_chap_credentials @@ -6786,6 +7489,45 @@ - [ ] update_web_acl - [ ] update_xss_match_set +## wafv2 +0% implemented +- [ ] associate_web_acl +- [ ] check_capacity +- [ ] create_ip_set +- [ ] create_regex_pattern_set +- [ ] create_rule_group +- [ ] create_web_acl +- [ ] delete_ip_set +- [ ] delete_logging_configuration +- [ ] delete_regex_pattern_set +- [ ] delete_rule_group +- [ ] delete_web_acl +- [ ] describe_managed_rule_group +- [ ] disassociate_web_acl +- [ ] get_ip_set +- [ ] get_logging_configuration +- [ ] get_rate_based_statement_managed_keys +- [ ] get_regex_pattern_set +- [ ] get_rule_group +- [ ] get_sampled_requests +- [ ] get_web_acl +- [ ] get_web_acl_for_resource +- [ ] list_available_managed_rule_groups +- [ ] list_ip_sets +- [ ] list_logging_configurations +- [ ] list_regex_pattern_sets +- [ ] list_resources_for_web_acl +- [ ] list_rule_groups +- [ ] list_tags_for_resource +- [ ] list_web_acls +- [ ] put_logging_configuration +- [ ] tag_resource +- [ ] untag_resource +- [ ] update_ip_set +- [ ] update_regex_pattern_set +- [ ] update_rule_group +- [ ] update_web_acl + ## workdocs 0% implemented - [ ] abort_document_version_upload diff --git a/moto/__init__.py b/moto/__init__.py index 767c0ee27..44b25f41e 100644 --- a/moto/__init__.py +++ b/moto/__init__.py @@ -9,6 +9,8 @@ from .batch import mock_batch # noqa from .cloudformation import mock_cloudformation # noqa from .cloudformation import mock_cloudformation_deprecated # noqa from .cloudwatch import mock_cloudwatch, mock_cloudwatch_deprecated # noqa +from .codecommit import mock_codecommit # noqa +from .codepipeline import mock_codepipeline # noqa from .cognitoidentity import mock_cognitoidentity # noqa from .cognitoidentity import mock_cognitoidentity_deprecated # noqa from .cognitoidp import mock_cognitoidp, mock_cognitoidp_deprecated # noqa @@ -20,6 +22,7 @@ from .dynamodb import mock_dynamodb, mock_dynamodb_deprecated # noqa from .dynamodb2 import mock_dynamodb2, mock_dynamodb2_deprecated # noqa from .dynamodbstreams import mock_dynamodbstreams # noqa from .ec2 import mock_ec2, mock_ec2_deprecated # noqa +from .ec2_instance_connect import mock_ec2_instance_connect # noqa from .ecr import mock_ecr, mock_ecr_deprecated # noqa from .ecs import mock_ecs, mock_ecs_deprecated # noqa from .elb import mock_elb, mock_elb_deprecated # noqa diff --git a/moto/acm/models.py b/moto/acm/models.py index a85017040..3df541982 100644 --- a/moto/acm/models.py +++ b/moto/acm/models.py @@ -13,8 +13,9 @@ import cryptography.hazmat.primitives.asymmetric.rsa from cryptography.hazmat.primitives import serialization, hashes from cryptography.hazmat.backends import default_backend +from moto.core import ACCOUNT_ID as DEFAULT_ACCOUNT_ID + -DEFAULT_ACCOUNT_ID = 123456789012 GOOGLE_ROOT_CA = b"""-----BEGIN CERTIFICATE----- MIIEKDCCAxCgAwIBAgIQAQAhJYiw+lmnd+8Fe2Yn3zANBgkqhkiG9w0BAQsFADBC MQswCQYDVQQGEwJVUzEWMBQGA1UEChMNR2VvVHJ1c3QgSW5jLjEbMBkGA1UEAxMS diff --git a/moto/apigateway/exceptions.py b/moto/apigateway/exceptions.py index 52c26fa46..434ebc467 100644 --- a/moto/apigateway/exceptions.py +++ b/moto/apigateway/exceptions.py @@ -101,3 +101,12 @@ class ApiKeyNotFoundException(RESTError): super(ApiKeyNotFoundException, self).__init__( "NotFoundException", "Invalid API Key identifier specified" ) + + +class ApiKeyAlreadyExists(RESTError): + code = 409 + + def __init__(self): + super(ApiKeyAlreadyExists, self).__init__( + "ConflictException", "API Key already exists" + ) diff --git a/moto/apigateway/models.py b/moto/apigateway/models.py index 6f1d01c4f..fd2fb7064 100644 --- a/moto/apigateway/models.py +++ b/moto/apigateway/models.py @@ -32,6 +32,7 @@ from .exceptions import ( RoleNotSpecified, NoIntegrationDefined, NoMethodDefined, + ApiKeyAlreadyExists, ) STAGE_URL = "https://{api_id}.execute-api.{region_name}.amazonaws.com/{stage_name}" @@ -759,6 +760,10 @@ class APIGatewayBackend(BaseBackend): return api.delete_deployment(deployment_id) def create_apikey(self, payload): + if payload.get("value") is not None: + for api_key in self.get_apikeys(): + if api_key.get("value") == payload["value"]: + raise ApiKeyAlreadyExists() key = ApiKey(**payload) self.keys[key["id"]] = key return key @@ -842,3 +847,11 @@ class APIGatewayBackend(BaseBackend): apigateway_backends = {} for region_name in Session().get_available_regions("apigateway"): apigateway_backends[region_name] = APIGatewayBackend(region_name) +for region_name in Session().get_available_regions( + "apigateway", partition_name="aws-us-gov" +): + apigateway_backends[region_name] = APIGatewayBackend(region_name) +for region_name in Session().get_available_regions( + "apigateway", partition_name="aws-cn" +): + apigateway_backends[region_name] = APIGatewayBackend(region_name) diff --git a/moto/apigateway/responses.py b/moto/apigateway/responses.py index 1089de211..c4c7b403e 100644 --- a/moto/apigateway/responses.py +++ b/moto/apigateway/responses.py @@ -9,6 +9,7 @@ from .exceptions import ( BadRequestException, CrossAccountNotAllowed, StageNotFoundException, + ApiKeyAlreadyExists, ) @@ -302,7 +303,17 @@ class APIGatewayResponse(BaseResponse): self.setup_class(request, full_url, headers) if self.method == "POST": - apikey_response = self.backend.create_apikey(json.loads(self.body)) + try: + apikey_response = self.backend.create_apikey(json.loads(self.body)) + except ApiKeyAlreadyExists as error: + return ( + error.code, + self.headers, + '{{"message":"{0}","code":"{1}"}}'.format( + error.message, error.error_type + ), + ) + elif self.method == "GET": apikeys_response = self.backend.get_apikeys() return 200, {}, json.dumps({"item": apikeys_response}) diff --git a/moto/athena/models.py b/moto/athena/models.py index 7353e6a6e..6aeca0ffa 100644 --- a/moto/athena/models.py +++ b/moto/athena/models.py @@ -1,10 +1,11 @@ from __future__ import unicode_literals import time -import boto3 +from boto3 import Session + from moto.core import BaseBackend, BaseModel -ACCOUNT_ID = 123456789012 +from moto.core import ACCOUNT_ID class TaggableResourceMixin(object): @@ -77,5 +78,9 @@ class AthenaBackend(BaseBackend): athena_backends = {} -for region in boto3.Session().get_available_regions("athena"): +for region in Session().get_available_regions("athena"): + athena_backends[region] = AthenaBackend(region) +for region in Session().get_available_regions("athena", partition_name="aws-us-gov"): + athena_backends[region] = AthenaBackend(region) +for region in Session().get_available_regions("athena", partition_name="aws-cn"): athena_backends[region] = AthenaBackend(region) diff --git a/moto/awslambda/models.py b/moto/awslambda/models.py index 7a9e90f9d..95a5c4ad5 100644 --- a/moto/awslambda/models.py +++ b/moto/awslambda/models.py @@ -23,7 +23,8 @@ import traceback import weakref import requests.adapters -import boto.awslambda +from boto3 import Session + from moto.core import BaseBackend, BaseModel from moto.core.exceptions import RESTError from moto.iam.models import iam_backend @@ -42,11 +43,10 @@ from .utils import make_function_arn, make_function_ver_arn from moto.sqs import sqs_backends from moto.dynamodb2 import dynamodb_backends2 from moto.dynamodbstreams import dynamodbstreams_backends +from moto.core import ACCOUNT_ID logger = logging.getLogger(__name__) -ACCOUNT_ID = "123456789012" - try: from tempfile import TemporaryDirectory @@ -1044,10 +1044,10 @@ def do_validate_s3(): return os.environ.get("VALIDATE_LAMBDA_S3", "") in ["", "1", "true"] -# Handle us forgotten regions, unless Lambda truly only runs out of US and -lambda_backends = { - _region.name: LambdaBackend(_region.name) for _region in boto.awslambda.regions() -} - -lambda_backends["ap-southeast-2"] = LambdaBackend("ap-southeast-2") -lambda_backends["us-gov-west-1"] = LambdaBackend("us-gov-west-1") +lambda_backends = {} +for region in Session().get_available_regions("lambda"): + lambda_backends[region] = LambdaBackend(region) +for region in Session().get_available_regions("lambda", partition_name="aws-us-gov"): + lambda_backends[region] = LambdaBackend(region) +for region in Session().get_available_regions("lambda", partition_name="aws-cn"): + lambda_backends[region] = LambdaBackend(region) diff --git a/moto/backends.py b/moto/backends.py index 53a5cafc3..a358b8fd2 100644 --- a/moto/backends.py +++ b/moto/backends.py @@ -8,6 +8,8 @@ from moto.awslambda import lambda_backends from moto.batch import batch_backends from moto.cloudformation import cloudformation_backends from moto.cloudwatch import cloudwatch_backends +from moto.codecommit import codecommit_backends +from moto.codepipeline import codepipeline_backends from moto.cognitoidentity import cognitoidentity_backends from moto.cognitoidp import cognitoidp_backends from moto.config import config_backends @@ -18,6 +20,7 @@ from moto.dynamodb import dynamodb_backends from moto.dynamodb2 import dynamodb_backends2 from moto.dynamodbstreams import dynamodbstreams_backends from moto.ec2 import ec2_backends +from moto.ec2_instance_connect import ec2_instance_connect_backends from moto.ecr import ecr_backends from moto.ecs import ecs_backends from moto.elb import elb_backends @@ -60,6 +63,8 @@ BACKENDS = { "batch": batch_backends, "cloudformation": cloudformation_backends, "cloudwatch": cloudwatch_backends, + "codecommit": codecommit_backends, + "codepipeline": codepipeline_backends, "cognito-identity": cognitoidentity_backends, "cognito-idp": cognitoidp_backends, "config": config_backends, @@ -69,6 +74,7 @@ BACKENDS = { "dynamodb2": dynamodb_backends2, "dynamodbstreams": dynamodbstreams_backends, "ec2": ec2_backends, + "ec2_instance_connect": ec2_instance_connect_backends, "ecr": ecr_backends, "ecs": ecs_backends, "elb": elb_backends, diff --git a/moto/batch/models.py b/moto/batch/models.py index ab52db54c..fc35f2997 100644 --- a/moto/batch/models.py +++ b/moto/batch/models.py @@ -1,5 +1,4 @@ from __future__ import unicode_literals -import boto3 import re import requests.adapters from itertools import cycle @@ -12,6 +11,8 @@ import docker import functools import threading import dateutil.parser +from boto3 import Session + from moto.core import BaseBackend, BaseModel from moto.iam import iam_backends from moto.ec2 import ec2_backends @@ -28,11 +29,10 @@ from .utils import ( from moto.ec2.exceptions import InvalidSubnetIdError from moto.ec2.models import INSTANCE_TYPES as EC2_INSTANCE_TYPES from moto.iam.exceptions import IAMNotFoundException - +from moto.core import ACCOUNT_ID as DEFAULT_ACCOUNT_ID _orig_adapter_send = requests.adapters.HTTPAdapter.send logger = logging.getLogger(__name__) -DEFAULT_ACCOUNT_ID = 123456789012 COMPUTE_ENVIRONMENT_NAME_REGEX = re.compile( r"^[A-Za-z0-9][A-Za-z0-9_-]{1,126}[A-Za-z0-9]$" ) @@ -182,7 +182,7 @@ class JobDefinition(BaseModel): self._region = region_name self.container_properties = container_properties self.arn = None - self.status = "INACTIVE" + self.status = "ACTIVE" if parameters is None: parameters = {} @@ -285,7 +285,7 @@ class JobDefinition(BaseModel): class Job(threading.Thread, BaseModel): - def __init__(self, name, job_def, job_queue, log_backend): + def __init__(self, name, job_def, job_queue, log_backend, container_overrides): """ Docker Job @@ -301,6 +301,7 @@ class Job(threading.Thread, BaseModel): self.job_name = name self.job_id = str(uuid.uuid4()) self.job_definition = job_def + self.container_overrides = container_overrides self.job_queue = job_queue self.job_state = "SUBMITTED" # One of SUBMITTED | PENDING | RUNNABLE | STARTING | RUNNING | SUCCEEDED | FAILED self.job_queue.jobs.append(self) @@ -357,6 +358,11 @@ class Job(threading.Thread, BaseModel): result["statusReason"] = self.job_stopped_reason return result + def _get_container_property(self, p, default): + return self.container_overrides.get( + p, self.job_definition.container_properties.get(p, default) + ) + def run(self): """ Run the container. @@ -375,8 +381,33 @@ class Job(threading.Thread, BaseModel): self.job_state = "PENDING" time.sleep(1) - image = "alpine:latest" - cmd = '/bin/sh -c "for a in `seq 1 10`; do echo Hello World; sleep 1; done"' + image = self.job_definition.container_properties.get( + "image", "alpine:latest" + ) + privileged = self.job_definition.container_properties.get( + "privileged", False + ) + cmd = self._get_container_property( + "command", + '/bin/sh -c "for a in `seq 1 10`; do echo Hello World; sleep 1; done"', + ) + environment = { + e["name"]: e["value"] + for e in self._get_container_property("environment", []) + } + volumes = { + v["name"]: v["host"] + for v in self._get_container_property("volumes", []) + } + mounts = [ + docker.types.Mount( + m["containerPath"], + volumes[m["sourceVolume"]]["sourcePath"], + type="bind", + read_only=m["readOnly"], + ) + for m in self._get_container_property("mountPoints", []) + ] name = "{0}-{1}".format(self.job_name, self.job_id) self.job_state = "RUNNABLE" @@ -384,8 +415,16 @@ class Job(threading.Thread, BaseModel): time.sleep(1) self.job_state = "STARTING" + log_config = docker.types.LogConfig(type=docker.types.LogConfig.types.JSON) container = self.docker_client.containers.run( - image, cmd, detach=True, name=name + image, + cmd, + detach=True, + name=name, + log_config=log_config, + environment=environment, + mounts=mounts, + privileged=privileged, ) self.job_state = "RUNNING" self.job_started_at = datetime.datetime.now() @@ -815,8 +854,10 @@ class BatchBackend(BaseBackend): raise InvalidParameterValueException( "computeResources must contain {0}".format(param) ) - - if self.iam_backend.get_role_by_arn(cr["instanceRole"]) is None: + for profile in self.iam_backend.get_instance_profiles(): + if profile.arn == cr["instanceRole"]: + break + else: raise InvalidParameterValueException( "could not find instanceRole {0}".format(cr["instanceRole"]) ) @@ -1208,7 +1249,13 @@ class BatchBackend(BaseBackend): if queue is None: raise ClientException("Job queue {0} does not exist".format(job_queue)) - job = Job(job_name, job_def, queue, log_backend=self.logs_backend) + job = Job( + job_name, + job_def, + queue, + log_backend=self.logs_backend, + container_overrides=container_overrides, + ) self._jobs[job.job_id] = job # Here comes the fun @@ -1271,7 +1318,10 @@ class BatchBackend(BaseBackend): job.terminate(reason) -available_regions = boto3.session.Session().get_available_regions("batch") -batch_backends = { - region: BatchBackend(region_name=region) for region in available_regions -} +batch_backends = {} +for region in Session().get_available_regions("batch"): + batch_backends[region] = BatchBackend(region) +for region in Session().get_available_regions("batch", partition_name="aws-us-gov"): + batch_backends[region] = BatchBackend(region) +for region in Session().get_available_regions("batch", partition_name="aws-cn"): + batch_backends[region] = BatchBackend(region) diff --git a/moto/cloudformation/models.py b/moto/cloudformation/models.py index 71ceaf168..0ae5d1ae4 100644 --- a/moto/cloudformation/models.py +++ b/moto/cloudformation/models.py @@ -4,7 +4,8 @@ import json import yaml import uuid -import boto.cloudformation +from boto3 import Session + from moto.compat import OrderedDict from moto.core import BaseBackend, BaseModel @@ -717,5 +718,13 @@ class CloudFormationBackend(BaseBackend): cloudformation_backends = {} -for region in boto.cloudformation.regions(): - cloudformation_backends[region.name] = CloudFormationBackend() +for region in Session().get_available_regions("cloudformation"): + cloudformation_backends[region] = CloudFormationBackend() +for region in Session().get_available_regions( + "cloudformation", partition_name="aws-us-gov" +): + cloudformation_backends[region] = CloudFormationBackend() +for region in Session().get_available_regions( + "cloudformation", partition_name="aws-cn" +): + cloudformation_backends[region] = CloudFormationBackend() diff --git a/moto/cloudformation/parsing.py b/moto/cloudformation/parsing.py index 77e3c271c..34d96acc6 100644 --- a/moto/cloudformation/parsing.py +++ b/moto/cloudformation/parsing.py @@ -1,5 +1,4 @@ from __future__ import unicode_literals -import collections import functools import logging import copy @@ -11,6 +10,7 @@ from moto.awslambda import models as lambda_models from moto.batch import models as batch_models from moto.cloudwatch import models as cloudwatch_models from moto.cognitoidentity import models as cognitoidentity_models +from moto.compat import collections_abc from moto.datapipeline import models as datapipeline_models from moto.dynamodb2 import models as dynamodb2_models from moto.ec2 import models as ec2_models @@ -27,6 +27,7 @@ from moto.route53 import models as route53_models from moto.s3 import models as s3_models from moto.sns import models as sns_models from moto.sqs import models as sqs_models +from moto.core import ACCOUNT_ID from .utils import random_suffix from .exceptions import ( ExportNotFound, @@ -404,7 +405,7 @@ def parse_output(output_logical_id, output_json, resources_map): return output -class ResourceMap(collections.Mapping): +class ResourceMap(collections_abc.Mapping): """ This is a lazy loading map for resources. This allows us to create resources without needing to create a full dependency tree. Upon creation, each @@ -431,7 +432,7 @@ class ResourceMap(collections.Mapping): # Create the default resources self._parsed_resources = { - "AWS::AccountId": "123456789012", + "AWS::AccountId": ACCOUNT_ID, "AWS::Region": self._region_name, "AWS::StackId": stack_id, "AWS::StackName": stack_name, @@ -633,7 +634,7 @@ class ResourceMap(collections.Mapping): raise last_exception -class OutputMap(collections.Mapping): +class OutputMap(collections_abc.Mapping): def __init__(self, resources, template, stack_id): self._template = template self._stack_id = stack_id diff --git a/moto/cloudformation/responses.py b/moto/cloudformation/responses.py index f5e094c15..bf68a6325 100644 --- a/moto/cloudformation/responses.py +++ b/moto/cloudformation/responses.py @@ -7,6 +7,7 @@ from six.moves.urllib.parse import urlparse from moto.core.responses import BaseResponse from moto.core.utils import amzn_request_id from moto.s3 import s3_backend +from moto.core import ACCOUNT_ID from .models import cloudformation_backends from .exceptions import ValidationError @@ -425,7 +426,9 @@ class CloudFormationResponse(BaseResponse): stackset = self.cloudformation_backend.get_stack_set(stackset_name) if not stackset.admin_role: - stackset.admin_role = "arn:aws:iam::123456789012:role/AWSCloudFormationStackSetAdministrationRole" + stackset.admin_role = "arn:aws:iam::{AccountId}:role/AWSCloudFormationStackSetAdministrationRole".format( + AccountId=ACCOUNT_ID + ) if not stackset.execution_role: stackset.execution_role = "AWSCloudFormationStackSetExecutionRole" @@ -1051,11 +1054,14 @@ STOP_STACK_SET_OPERATION_RESPONSE_TEMPLATE = """ """ -DESCRIBE_STACKSET_OPERATION_RESPONSE_TEMPLATE = """ +DESCRIBE_STACKSET_OPERATION_RESPONSE_TEMPLATE = ( + """ {{ stackset.execution_role }} - arn:aws:iam::123456789012:role/{{ stackset.admin_role }} + arn:aws:iam::""" + + ACCOUNT_ID + + """:role/{{ stackset.admin_role }} {{ stackset.id }} {{ operation.CreationTimestamp }} {{ operation.OperationId }} @@ -1072,15 +1078,19 @@ DESCRIBE_STACKSET_OPERATION_RESPONSE_TEMPLATE = """ """ +) -LIST_STACK_SET_OPERATION_RESULTS_RESPONSE_TEMPLATE = """ +LIST_STACK_SET_OPERATION_RESULTS_RESPONSE_TEMPLATE = ( + """ {% for instance in operation.Instances %} {% for account, region in instance.items() %} - Function not found: arn:aws:lambda:us-west-2:123456789012:function:AWSCloudFormationStackSetAccountGate + Function not found: arn:aws:lambda:us-west-2:""" + + ACCOUNT_ID + + """:function:AWSCloudFormationStackSetAccountGate SKIPPED {{ region }} @@ -1096,3 +1106,4 @@ LIST_STACK_SET_OPERATION_RESULTS_RESPONSE_TEMPLATE = """ """ +) diff --git a/moto/cloudformation/utils.py b/moto/cloudformation/utils.py index 42dfa0b63..cd8481002 100644 --- a/moto/cloudformation/utils.py +++ b/moto/cloudformation/utils.py @@ -7,6 +7,7 @@ import os import string from cfnlint import decode, core +from moto.core import ACCOUNT_ID def generate_stack_id(stack_name, region="us-east-1", account="123456789"): @@ -29,8 +30,8 @@ def generate_stackset_id(stackset_name): def generate_stackset_arn(stackset_id, region_name): - return "arn:aws:cloudformation:{}:123456789012:stackset/{}".format( - region_name, stackset_id + return "arn:aws:cloudformation:{}:{}:stackset/{}".format( + region_name, ACCOUNT_ID, stackset_id ) diff --git a/moto/cloudwatch/models.py b/moto/cloudwatch/models.py index 7566e757b..13b31ddfe 100644 --- a/moto/cloudwatch/models.py +++ b/moto/cloudwatch/models.py @@ -1,14 +1,16 @@ import json + +from boto3 import Session + from moto.core.utils import iso_8601_datetime_with_milliseconds from moto.core import BaseBackend, BaseModel from moto.core.exceptions import RESTError -import boto.ec2.cloudwatch from datetime import datetime, timedelta from dateutil.tz import tzutc from uuid import uuid4 from .utils import make_arn_for_dashboard -DEFAULT_ACCOUNT_ID = 123456789012 +from moto.core import ACCOUNT_ID as DEFAULT_ACCOUNT_ID _EMPTY_LIST = tuple() @@ -431,5 +433,11 @@ class LogGroup(BaseModel): cloudwatch_backends = {} -for region in boto.ec2.cloudwatch.regions(): - cloudwatch_backends[region.name] = CloudWatchBackend() +for region in Session().get_available_regions("cloudwatch"): + cloudwatch_backends[region] = CloudWatchBackend() +for region in Session().get_available_regions( + "cloudwatch", partition_name="aws-us-gov" +): + cloudwatch_backends[region] = CloudWatchBackend() +for region in Session().get_available_regions("cloudwatch", partition_name="aws-cn"): + cloudwatch_backends[region] = CloudWatchBackend() diff --git a/moto/codecommit/__init__.py b/moto/codecommit/__init__.py new file mode 100644 index 000000000..6c5a8f5ad --- /dev/null +++ b/moto/codecommit/__init__.py @@ -0,0 +1,4 @@ +from .models import codecommit_backends +from ..core.models import base_decorator + +mock_codecommit = base_decorator(codecommit_backends) diff --git a/moto/codecommit/exceptions.py b/moto/codecommit/exceptions.py new file mode 100644 index 000000000..136af50f1 --- /dev/null +++ b/moto/codecommit/exceptions.py @@ -0,0 +1,35 @@ +from moto.core.exceptions import JsonRESTError + + +class RepositoryNameExistsException(JsonRESTError): + code = 400 + + def __init__(self, repository_name): + super(RepositoryNameExistsException, self).__init__( + "RepositoryNameExistsException", + "Repository named {0} already exists".format(repository_name), + ) + + +class RepositoryDoesNotExistException(JsonRESTError): + code = 400 + + def __init__(self, repository_name): + super(RepositoryDoesNotExistException, self).__init__( + "RepositoryDoesNotExistException", + "{0} does not exist".format(repository_name), + ) + + +class InvalidRepositoryNameException(JsonRESTError): + code = 400 + + def __init__(self): + super(InvalidRepositoryNameException, self).__init__( + "InvalidRepositoryNameException", + "The repository name is not valid. Repository names can be any valid " + "combination of letters, numbers, " + "periods, underscores, and dashes between 1 and 100 characters in " + "length. Names are case sensitive. " + "For more information, see Limits in the AWS CodeCommit User Guide. ", + ) diff --git a/moto/codecommit/models.py b/moto/codecommit/models.py new file mode 100644 index 000000000..6a4e82ad2 --- /dev/null +++ b/moto/codecommit/models.py @@ -0,0 +1,69 @@ +from boto3 import Session +from moto.core import BaseBackend, BaseModel +from moto.core.utils import iso_8601_datetime_with_milliseconds +from datetime import datetime +from moto.iam.models import ACCOUNT_ID +from .exceptions import RepositoryDoesNotExistException, RepositoryNameExistsException +import uuid + + +class CodeCommit(BaseModel): + def __init__(self, region, repository_description, repository_name): + current_date = iso_8601_datetime_with_milliseconds(datetime.utcnow()) + self.repository_metadata = dict() + self.repository_metadata["repositoryName"] = repository_name + self.repository_metadata[ + "cloneUrlSsh" + ] = "ssh://git-codecommit.{0}.amazonaws.com/v1/repos/{1}".format( + region, repository_name + ) + self.repository_metadata[ + "cloneUrlHttp" + ] = "https://git-codecommit.{0}.amazonaws.com/v1/repos/{1}".format( + region, repository_name + ) + self.repository_metadata["creationDate"] = current_date + self.repository_metadata["lastModifiedDate"] = current_date + self.repository_metadata["repositoryDescription"] = repository_description + self.repository_metadata["repositoryId"] = str(uuid.uuid4()) + self.repository_metadata["Arn"] = "arn:aws:codecommit:{0}:{1}:{2}".format( + region, ACCOUNT_ID, repository_name + ) + self.repository_metadata["accountId"] = ACCOUNT_ID + + +class CodeCommitBackend(BaseBackend): + def __init__(self): + self.repositories = {} + + def create_repository(self, region, repository_name, repository_description): + repository = self.repositories.get(repository_name) + if repository: + raise RepositoryNameExistsException(repository_name) + + self.repositories[repository_name] = CodeCommit( + region, repository_description, repository_name + ) + + return self.repositories[repository_name].repository_metadata + + def get_repository(self, repository_name): + repository = self.repositories.get(repository_name) + if not repository: + raise RepositoryDoesNotExistException(repository_name) + + return repository.repository_metadata + + def delete_repository(self, repository_name): + repository = self.repositories.get(repository_name) + + if repository: + self.repositories.pop(repository_name) + return repository.repository_metadata.get("repositoryId") + + return None + + +codecommit_backends = {} +for region in Session().get_available_regions("codecommit"): + codecommit_backends[region] = CodeCommitBackend() diff --git a/moto/codecommit/responses.py b/moto/codecommit/responses.py new file mode 100644 index 000000000..3c6fdc5ea --- /dev/null +++ b/moto/codecommit/responses.py @@ -0,0 +1,57 @@ +import json +import re + +from moto.core.responses import BaseResponse +from .models import codecommit_backends +from .exceptions import InvalidRepositoryNameException + + +def _is_repository_name_valid(repository_name): + name_regex = re.compile(r"[\w\.-]+") + result = name_regex.split(repository_name) + if len(result) > 0: + for match in result: + if len(match) > 0: + return False + return True + + +class CodeCommitResponse(BaseResponse): + @property + def codecommit_backend(self): + return codecommit_backends[self.region] + + def create_repository(self): + if not _is_repository_name_valid(self._get_param("repositoryName")): + raise InvalidRepositoryNameException() + + repository_metadata = self.codecommit_backend.create_repository( + self.region, + self._get_param("repositoryName"), + self._get_param("repositoryDescription"), + ) + + return json.dumps({"repositoryMetadata": repository_metadata}) + + def get_repository(self): + if not _is_repository_name_valid(self._get_param("repositoryName")): + raise InvalidRepositoryNameException() + + repository_metadata = self.codecommit_backend.get_repository( + self._get_param("repositoryName") + ) + + return json.dumps({"repositoryMetadata": repository_metadata}) + + def delete_repository(self): + if not _is_repository_name_valid(self._get_param("repositoryName")): + raise InvalidRepositoryNameException() + + repository_id = self.codecommit_backend.delete_repository( + self._get_param("repositoryName") + ) + + if repository_id: + return json.dumps({"repositoryId": repository_id}) + + return json.dumps({}) diff --git a/moto/codecommit/urls.py b/moto/codecommit/urls.py new file mode 100644 index 000000000..1e3cdb1b4 --- /dev/null +++ b/moto/codecommit/urls.py @@ -0,0 +1,6 @@ +from __future__ import unicode_literals +from .responses import CodeCommitResponse + +url_bases = ["https?://codecommit.(.+).amazonaws.com"] + +url_paths = {"{0}/$": CodeCommitResponse.dispatch} diff --git a/moto/codepipeline/__init__.py b/moto/codepipeline/__init__.py new file mode 100644 index 000000000..da1f1fa9d --- /dev/null +++ b/moto/codepipeline/__init__.py @@ -0,0 +1,4 @@ +from .models import codepipeline_backends +from ..core.models import base_decorator + +mock_codepipeline = base_decorator(codepipeline_backends) diff --git a/moto/codepipeline/exceptions.py b/moto/codepipeline/exceptions.py new file mode 100644 index 000000000..a4db9aab1 --- /dev/null +++ b/moto/codepipeline/exceptions.py @@ -0,0 +1,44 @@ +from moto.core.exceptions import JsonRESTError + + +class InvalidStructureException(JsonRESTError): + code = 400 + + def __init__(self, message): + super(InvalidStructureException, self).__init__( + "InvalidStructureException", message + ) + + +class PipelineNotFoundException(JsonRESTError): + code = 400 + + def __init__(self, message): + super(PipelineNotFoundException, self).__init__( + "PipelineNotFoundException", message + ) + + +class ResourceNotFoundException(JsonRESTError): + code = 400 + + def __init__(self, message): + super(ResourceNotFoundException, self).__init__( + "ResourceNotFoundException", message + ) + + +class InvalidTagsException(JsonRESTError): + code = 400 + + def __init__(self, message): + super(InvalidTagsException, self).__init__("InvalidTagsException", message) + + +class TooManyTagsException(JsonRESTError): + code = 400 + + def __init__(self, arn): + super(TooManyTagsException, self).__init__( + "TooManyTagsException", "Tag limit exceeded for resource [{}].".format(arn) + ) diff --git a/moto/codepipeline/models.py b/moto/codepipeline/models.py new file mode 100644 index 000000000..50f07deb0 --- /dev/null +++ b/moto/codepipeline/models.py @@ -0,0 +1,218 @@ +import json +from datetime import datetime + +from boto3 import Session +from moto.core.utils import iso_8601_datetime_with_milliseconds + +from moto.iam.exceptions import IAMNotFoundException + +from moto.iam import iam_backends + +from moto.codepipeline.exceptions import ( + InvalidStructureException, + PipelineNotFoundException, + ResourceNotFoundException, + InvalidTagsException, + TooManyTagsException, +) +from moto.core import BaseBackend, BaseModel + +from moto.iam.models import ACCOUNT_ID + + +class CodePipeline(BaseModel): + def __init__(self, region, pipeline): + # the version number for a new pipeline is always 1 + pipeline["version"] = 1 + + self.pipeline = self.add_default_values(pipeline) + self.tags = {} + + self._arn = "arn:aws:codepipeline:{0}:{1}:{2}".format( + region, ACCOUNT_ID, pipeline["name"] + ) + self._created = datetime.utcnow() + self._updated = datetime.utcnow() + + @property + def metadata(self): + return { + "pipelineArn": self._arn, + "created": iso_8601_datetime_with_milliseconds(self._created), + "updated": iso_8601_datetime_with_milliseconds(self._updated), + } + + def add_default_values(self, pipeline): + for stage in pipeline["stages"]: + for action in stage["actions"]: + if "runOrder" not in action: + action["runOrder"] = 1 + if "configuration" not in action: + action["configuration"] = {} + if "outputArtifacts" not in action: + action["outputArtifacts"] = [] + if "inputArtifacts" not in action: + action["inputArtifacts"] = [] + + return pipeline + + def validate_tags(self, tags): + for tag in tags: + if tag["key"].startswith("aws:"): + raise InvalidTagsException( + "Not allowed to modify system tags. " + "System tags start with 'aws:'. " + "msg=[Caller is an end user and not allowed to mutate system tags]" + ) + + if (len(self.tags) + len(tags)) > 50: + raise TooManyTagsException(self._arn) + + +class CodePipelineBackend(BaseBackend): + def __init__(self): + self.pipelines = {} + + @property + def iam_backend(self): + return iam_backends["global"] + + def create_pipeline(self, region, pipeline, tags): + if pipeline["name"] in self.pipelines: + raise InvalidStructureException( + "A pipeline with the name '{0}' already exists in account '{1}'".format( + pipeline["name"], ACCOUNT_ID + ) + ) + + try: + role = self.iam_backend.get_role_by_arn(pipeline["roleArn"]) + service_principal = json.loads(role.assume_role_policy_document)[ + "Statement" + ][0]["Principal"]["Service"] + if "codepipeline.amazonaws.com" not in service_principal: + raise IAMNotFoundException("") + except IAMNotFoundException: + raise InvalidStructureException( + "CodePipeline is not authorized to perform AssumeRole on role {}".format( + pipeline["roleArn"] + ) + ) + + if len(pipeline["stages"]) < 2: + raise InvalidStructureException( + "Pipeline has only 1 stage(s). There should be a minimum of 2 stages in a pipeline" + ) + + self.pipelines[pipeline["name"]] = CodePipeline(region, pipeline) + + if tags: + self.pipelines[pipeline["name"]].validate_tags(tags) + + new_tags = {tag["key"]: tag["value"] for tag in tags} + self.pipelines[pipeline["name"]].tags.update(new_tags) + + return pipeline, sorted(tags, key=lambda i: i["key"]) + + def get_pipeline(self, name): + codepipeline = self.pipelines.get(name) + + if not codepipeline: + raise PipelineNotFoundException( + "Account '{0}' does not have a pipeline with name '{1}'".format( + ACCOUNT_ID, name + ) + ) + + return codepipeline.pipeline, codepipeline.metadata + + def update_pipeline(self, pipeline): + codepipeline = self.pipelines.get(pipeline["name"]) + + if not codepipeline: + raise ResourceNotFoundException( + "The account with id '{0}' does not include a pipeline with the name '{1}'".format( + ACCOUNT_ID, pipeline["name"] + ) + ) + + # version number is auto incremented + pipeline["version"] = codepipeline.pipeline["version"] + 1 + codepipeline._updated = datetime.utcnow() + codepipeline.pipeline = codepipeline.add_default_values(pipeline) + + return codepipeline.pipeline + + def list_pipelines(self): + pipelines = [] + + for name, codepipeline in self.pipelines.items(): + pipelines.append( + { + "name": name, + "version": codepipeline.pipeline["version"], + "created": codepipeline.metadata["created"], + "updated": codepipeline.metadata["updated"], + } + ) + + return sorted(pipelines, key=lambda i: i["name"]) + + def delete_pipeline(self, name): + self.pipelines.pop(name, None) + + def list_tags_for_resource(self, arn): + name = arn.split(":")[-1] + pipeline = self.pipelines.get(name) + + if not pipeline: + raise ResourceNotFoundException( + "The account with id '{0}' does not include a pipeline with the name '{1}'".format( + ACCOUNT_ID, name + ) + ) + + tags = [{"key": key, "value": value} for key, value in pipeline.tags.items()] + + return sorted(tags, key=lambda i: i["key"]) + + def tag_resource(self, arn, tags): + name = arn.split(":")[-1] + pipeline = self.pipelines.get(name) + + if not pipeline: + raise ResourceNotFoundException( + "The account with id '{0}' does not include a pipeline with the name '{1}'".format( + ACCOUNT_ID, name + ) + ) + + pipeline.validate_tags(tags) + + for tag in tags: + pipeline.tags.update({tag["key"]: tag["value"]}) + + def untag_resource(self, arn, tag_keys): + name = arn.split(":")[-1] + pipeline = self.pipelines.get(name) + + if not pipeline: + raise ResourceNotFoundException( + "The account with id '{0}' does not include a pipeline with the name '{1}'".format( + ACCOUNT_ID, name + ) + ) + + for key in tag_keys: + pipeline.tags.pop(key, None) + + +codepipeline_backends = {} +for region in Session().get_available_regions("codepipeline"): + codepipeline_backends[region] = CodePipelineBackend() +for region in Session().get_available_regions( + "codepipeline", partition_name="aws-us-gov" +): + codepipeline_backends[region] = CodePipelineBackend() +for region in Session().get_available_regions("codepipeline", partition_name="aws-cn"): + codepipeline_backends[region] = CodePipelineBackend() diff --git a/moto/codepipeline/responses.py b/moto/codepipeline/responses.py new file mode 100644 index 000000000..0223dfae6 --- /dev/null +++ b/moto/codepipeline/responses.py @@ -0,0 +1,62 @@ +import json + +from moto.core.responses import BaseResponse +from .models import codepipeline_backends + + +class CodePipelineResponse(BaseResponse): + @property + def codepipeline_backend(self): + return codepipeline_backends[self.region] + + def create_pipeline(self): + pipeline, tags = self.codepipeline_backend.create_pipeline( + self.region, self._get_param("pipeline"), self._get_param("tags") + ) + + return json.dumps({"pipeline": pipeline, "tags": tags}) + + def get_pipeline(self): + pipeline, metadata = self.codepipeline_backend.get_pipeline( + self._get_param("name") + ) + + return json.dumps({"pipeline": pipeline, "metadata": metadata}) + + def update_pipeline(self): + pipeline = self.codepipeline_backend.update_pipeline( + self._get_param("pipeline") + ) + + return json.dumps({"pipeline": pipeline}) + + def list_pipelines(self): + pipelines = self.codepipeline_backend.list_pipelines() + + return json.dumps({"pipelines": pipelines}) + + def delete_pipeline(self): + self.codepipeline_backend.delete_pipeline(self._get_param("name")) + + return "" + + def list_tags_for_resource(self): + tags = self.codepipeline_backend.list_tags_for_resource( + self._get_param("resourceArn") + ) + + return json.dumps({"tags": tags}) + + def tag_resource(self): + self.codepipeline_backend.tag_resource( + self._get_param("resourceArn"), self._get_param("tags") + ) + + return "" + + def untag_resource(self): + self.codepipeline_backend.untag_resource( + self._get_param("resourceArn"), self._get_param("tagKeys") + ) + + return "" diff --git a/moto/codepipeline/urls.py b/moto/codepipeline/urls.py new file mode 100644 index 000000000..7111020a5 --- /dev/null +++ b/moto/codepipeline/urls.py @@ -0,0 +1,6 @@ +from __future__ import unicode_literals +from .responses import CodePipelineResponse + +url_bases = ["https?://codepipeline.(.+).amazonaws.com"] + +url_paths = {"{0}/$": CodePipelineResponse.dispatch} diff --git a/moto/cognitoidentity/models.py b/moto/cognitoidentity/models.py index 2a4f5d4bc..ae9f308c2 100644 --- a/moto/cognitoidentity/models.py +++ b/moto/cognitoidentity/models.py @@ -3,7 +3,7 @@ from __future__ import unicode_literals import datetime import json -import boto.cognito.identity +from boto3 import Session from moto.compat import OrderedDict from moto.core import BaseBackend, BaseModel @@ -136,5 +136,13 @@ class CognitoIdentityBackend(BaseBackend): cognitoidentity_backends = {} -for region in boto.cognito.identity.regions(): - cognitoidentity_backends[region.name] = CognitoIdentityBackend(region.name) +for region in Session().get_available_regions("cognito-identity"): + cognitoidentity_backends[region] = CognitoIdentityBackend(region) +for region in Session().get_available_regions( + "cognito-identity", partition_name="aws-us-gov" +): + cognitoidentity_backends[region] = CognitoIdentityBackend(region) +for region in Session().get_available_regions( + "cognito-identity", partition_name="aws-cn" +): + cognitoidentity_backends[region] = CognitoIdentityBackend(region) diff --git a/moto/cognitoidp/models.py b/moto/cognitoidp/models.py index 6700920ce..478ceffb2 100644 --- a/moto/cognitoidp/models.py +++ b/moto/cognitoidp/models.py @@ -9,7 +9,7 @@ import os import time import uuid -import boto.cognito.identity +from boto3 import Session from jose import jws from moto.compat import OrderedDict @@ -749,8 +749,14 @@ class CognitoIdpBackend(BaseBackend): cognitoidp_backends = {} -for region in boto.cognito.identity.regions(): - cognitoidp_backends[region.name] = CognitoIdpBackend(region.name) +for region in Session().get_available_regions("cognito-idp"): + cognitoidp_backends[region] = CognitoIdpBackend(region) +for region in Session().get_available_regions( + "cognito-idp", partition_name="aws-us-gov" +): + cognitoidp_backends[region] = CognitoIdpBackend(region) +for region in Session().get_available_regions("cognito-idp", partition_name="aws-cn"): + cognitoidp_backends[region] = CognitoIdpBackend(region) # Hack to help moto-server process requests on localhost, where the region isn't diff --git a/moto/compat.py b/moto/compat.py index d7f5ab5e6..c0acd28a6 100644 --- a/moto/compat.py +++ b/moto/compat.py @@ -3,3 +3,8 @@ try: except ImportError: # python 2.6 or earlier, use backport from ordereddict import OrderedDict # noqa + +try: + import collections.abc as collections_abc # noqa +except ImportError: + import collections as collections_abc # noqa diff --git a/moto/config/models.py b/moto/config/models.py index f608b759a..45dccd1ba 100644 --- a/moto/config/models.py +++ b/moto/config/models.py @@ -45,7 +45,8 @@ from moto.config.exceptions import ( from moto.core import BaseBackend, BaseModel from moto.s3.config import s3_config_query -DEFAULT_ACCOUNT_ID = "123456789012" +from moto.core import ACCOUNT_ID as DEFAULT_ACCOUNT_ID + POP_STRINGS = [ "capitalizeStart", "CapitalizeStart", @@ -1083,6 +1084,9 @@ class ConfigBackend(BaseBackend): config_backends = {} -boto3_session = Session() -for region in boto3_session.get_available_regions("config"): +for region in Session().get_available_regions("config"): + config_backends[region] = ConfigBackend() +for region in Session().get_available_regions("config", partition_name="aws-us-gov"): + config_backends[region] = ConfigBackend() +for region in Session().get_available_regions("config", partition_name="aws-cn"): config_backends[region] = ConfigBackend() diff --git a/moto/core/__init__.py b/moto/core/__init__.py index 4a4dfdfb6..045124fab 100644 --- a/moto/core/__init__.py +++ b/moto/core/__init__.py @@ -1,6 +1,6 @@ from __future__ import unicode_literals -from .models import BaseModel, BaseBackend, moto_api_backend # noqa +from .models import BaseModel, BaseBackend, moto_api_backend, ACCOUNT_ID # noqa from .responses import ActionAuthenticatorMixin moto_api_backends = {"global": moto_api_backend} diff --git a/moto/core/access_control.py b/moto/core/access_control.py index 9991063f9..8ba0c3ba1 100644 --- a/moto/core/access_control.py +++ b/moto/core/access_control.py @@ -24,7 +24,8 @@ from botocore.awsrequest import AWSRequest from botocore.credentials import Credentials from six import string_types -from moto.iam.models import ACCOUNT_ID, Policy +from moto.core import ACCOUNT_ID +from moto.iam.models import Policy from moto.iam import iam_backend from moto.core.exceptions import ( SignatureDoesNotMatchError, diff --git a/moto/core/models.py b/moto/core/models.py index 5b19137c3..3be3bbd8e 100644 --- a/moto/core/models.py +++ b/moto/core/models.py @@ -23,6 +23,9 @@ from .utils import ( ) +ACCOUNT_ID = os.environ.get("MOTO_ACCOUNT_ID", "123456789012") + + class BaseMockAWS(object): nested_count = 0 diff --git a/moto/core/utils.py b/moto/core/utils.py index 57ff0f1b4..efad5679c 100644 --- a/moto/core/utils.py +++ b/moto/core/utils.py @@ -304,3 +304,27 @@ def path_url(url): if parsed_url.query: path = path + "?" + parsed_url.query return path + + +def py2_strip_unicode_keys(blob): + """For Python 2 Only -- this will convert unicode keys in nested Dicts, Lists, and Sets to standard strings.""" + if type(blob) == unicode: # noqa + return str(blob) + + elif type(blob) == dict: + for key in list(blob.keys()): + value = blob.pop(key) + blob[str(key)] = py2_strip_unicode_keys(value) + + elif type(blob) == list: + for i in range(0, len(blob)): + blob[i] = py2_strip_unicode_keys(blob[i]) + + elif type(blob) == set: + new_set = set() + for value in blob: + new_set.add(py2_strip_unicode_keys(value)) + + blob = new_set + + return blob diff --git a/moto/datapipeline/models.py b/moto/datapipeline/models.py index cc1fe777e..d93deea61 100644 --- a/moto/datapipeline/models.py +++ b/moto/datapipeline/models.py @@ -1,7 +1,8 @@ from __future__ import unicode_literals import datetime -import boto.datapipeline +from boto3 import Session + from moto.compat import OrderedDict from moto.core import BaseBackend, BaseModel from .utils import get_random_pipeline_id, remove_capitalization_of_dict_keys @@ -142,5 +143,11 @@ class DataPipelineBackend(BaseBackend): datapipeline_backends = {} -for region in boto.datapipeline.regions(): - datapipeline_backends[region.name] = DataPipelineBackend() +for region in Session().get_available_regions("datapipeline"): + datapipeline_backends[region] = DataPipelineBackend() +for region in Session().get_available_regions( + "datapipeline", partition_name="aws-us-gov" +): + datapipeline_backends[region] = DataPipelineBackend() +for region in Session().get_available_regions("datapipeline", partition_name="aws-cn"): + datapipeline_backends[region] = DataPipelineBackend(region) diff --git a/moto/datapipeline/utils.py b/moto/datapipeline/utils.py index 9135181e7..b14fe6f1a 100644 --- a/moto/datapipeline/utils.py +++ b/moto/datapipeline/utils.py @@ -1,5 +1,5 @@ -import collections import six +from moto.compat import collections_abc from moto.core.utils import get_random_hex @@ -8,13 +8,13 @@ def get_random_pipeline_id(): def remove_capitalization_of_dict_keys(obj): - if isinstance(obj, collections.Mapping): + if isinstance(obj, collections_abc.Mapping): result = obj.__class__() for key, value in obj.items(): normalized_key = key[:1].lower() + key[1:] result[normalized_key] = remove_capitalization_of_dict_keys(value) return result - elif isinstance(obj, collections.Iterable) and not isinstance( + elif isinstance(obj, collections_abc.Iterable) and not isinstance( obj, six.string_types ): result = obj.__class__() diff --git a/moto/datasync/models.py b/moto/datasync/models.py index 17a2659fb..702cace5b 100644 --- a/moto/datasync/models.py +++ b/moto/datasync/models.py @@ -1,4 +1,5 @@ -import boto3 +from boto3 import Session + from moto.compat import OrderedDict from moto.core import BaseBackend, BaseModel @@ -226,5 +227,9 @@ class DataSyncBackend(BaseBackend): datasync_backends = {} -for region in boto3.Session().get_available_regions("datasync"): - datasync_backends[region] = DataSyncBackend(region_name=region) +for region in Session().get_available_regions("datasync"): + datasync_backends[region] = DataSyncBackend(region) +for region in Session().get_available_regions("datasync", partition_name="aws-us-gov"): + datasync_backends[region] = DataSyncBackend(region) +for region in Session().get_available_regions("datasync", partition_name="aws-cn"): + datasync_backends[region] = DataSyncBackend(region) diff --git a/moto/dynamodb/models.py b/moto/dynamodb/models.py index f00f6042d..f5771ec6e 100644 --- a/moto/dynamodb/models.py +++ b/moto/dynamodb/models.py @@ -6,6 +6,7 @@ import json from moto.compat import OrderedDict from moto.core import BaseBackend, BaseModel from moto.core.utils import unix_time +from moto.core import ACCOUNT_ID from .comparisons import get_comparison_func @@ -277,8 +278,8 @@ class Table(BaseModel): if attribute_name == "StreamArn": region = "us-east-1" time = "2000-01-01T00:00:00.000" - return "arn:aws:dynamodb:{0}:123456789012:table/{1}/stream/{2}".format( - region, self.name, time + return "arn:aws:dynamodb:{0}:{1}:table/{2}/stream/{3}".format( + region, ACCOUNT_ID, self.name, time ) raise UnformattedGetAttTemplateException() diff --git a/moto/dynamodb2/models.py b/moto/dynamodb2/models.py index 121f564a4..d4907cba5 100644 --- a/moto/dynamodb2/models.py +++ b/moto/dynamodb2/models.py @@ -8,7 +8,7 @@ import re import uuid import six -import boto3 +from boto3 import Session from botocore.exceptions import ParamValidationError from moto.compat import OrderedDict from moto.core import BaseBackend, BaseModel @@ -586,7 +586,9 @@ class StreamRecord(BaseModel): self.record["dynamodb"]["OldImage"] = old_a # This is a substantial overestimate but it's the easiest to do now - self.record["dynamodb"]["SizeBytes"] = len(json.dumps(self.record["dynamodb"])) + self.record["dynamodb"]["SizeBytes"] = len( + dynamo_json_dump(self.record["dynamodb"]) + ) def to_json(self): return self.record @@ -1484,7 +1486,10 @@ class DynamoDBBackend(BaseBackend): return table.ttl -available_regions = boto3.session.Session().get_available_regions("dynamodb") -dynamodb_backends = { - region: DynamoDBBackend(region_name=region) for region in available_regions -} +dynamodb_backends = {} +for region in Session().get_available_regions("dynamodb"): + dynamodb_backends[region] = DynamoDBBackend(region) +for region in Session().get_available_regions("dynamodb", partition_name="aws-us-gov"): + dynamodb_backends[region] = DynamoDBBackend(region) +for region in Session().get_available_regions("dynamodb", partition_name="aws-cn"): + dynamodb_backends[region] = DynamoDBBackend(region) diff --git a/moto/dynamodb2/responses.py b/moto/dynamodb2/responses.py index 0e39a1da1..c9f3529a9 100644 --- a/moto/dynamodb2/responses.py +++ b/moto/dynamodb2/responses.py @@ -438,9 +438,12 @@ class DynamoHandler(BaseResponse): all_indexes = (table.global_indexes or []) + (table.indexes or []) indexes_by_name = dict((i["IndexName"], i) for i in all_indexes) if index_name not in indexes_by_name: - raise ValueError( - "Invalid index: %s for table: %s. Available indexes are: %s" - % (index_name, name, ", ".join(indexes_by_name.keys())) + er = "com.amazonaws.dynamodb.v20120810#ResourceNotFoundException" + return self.error( + er, + "Invalid index: {} for table: {}. Available indexes are: {}".format( + index_name, name, ", ".join(indexes_by_name.keys()) + ), ) index = indexes_by_name[index_name]["KeySchema"] @@ -481,7 +484,9 @@ class DynamoHandler(BaseResponse): ] elif "begins_with" in range_key_expression: range_comparison = "BEGINS_WITH" - range_values = [value_alias_map[range_key_expression_components[1]]] + range_values = [ + value_alias_map[range_key_expression_components[-1]] + ] else: range_values = [value_alias_map[range_key_expression_components[2]]] else: diff --git a/moto/dynamodbstreams/models.py b/moto/dynamodbstreams/models.py index 6e99d8ef6..dc6f0e0d3 100644 --- a/moto/dynamodbstreams/models.py +++ b/moto/dynamodbstreams/models.py @@ -2,9 +2,10 @@ from __future__ import unicode_literals import os import json -import boto3 import base64 +from boto3 import Session + from moto.core import BaseBackend, BaseModel from moto.dynamodb2.models import dynamodb_backends @@ -139,7 +140,14 @@ class DynamoDBStreamsBackend(BaseBackend): return json.dumps(shard_iterator.get(limit)) -available_regions = boto3.session.Session().get_available_regions("dynamodbstreams") -dynamodbstreams_backends = { - region: DynamoDBStreamsBackend(region=region) for region in available_regions -} +dynamodbstreams_backends = {} +for region in Session().get_available_regions("dynamodbstreams"): + dynamodbstreams_backends[region] = DynamoDBStreamsBackend(region) +for region in Session().get_available_regions( + "dynamodbstreams", partition_name="aws-us-gov" +): + dynamodbstreams_backends[region] = DynamoDBStreamsBackend(region) +for region in Session().get_available_regions( + "dynamodbstreams", partition_name="aws-cn" +): + dynamodbstreams_backends[region] = DynamoDBStreamsBackend(region) diff --git a/moto/ec2/models.py b/moto/ec2/models.py index 874536225..93a350914 100644 --- a/moto/ec2/models.py +++ b/moto/ec2/models.py @@ -8,9 +8,9 @@ import os import re import six import warnings -from pkg_resources import resource_filename -import boto.ec2 +from boto3 import Session +from pkg_resources import resource_filename from collections import defaultdict import weakref @@ -1473,7 +1473,13 @@ class Zone(object): class RegionsAndZonesBackend(object): - regions = [Region(ri.name, ri.endpoint) for ri in boto.ec2.regions()] + regions = [] + for region in Session().get_available_regions("ec2"): + regions.append(Region(region, "ec2.{}.amazonaws.com".format(region))) + for region in Session().get_available_regions("ec2", partition_name="aws-us-gov"): + regions.append(Region(region, "ec2.{}.amazonaws.com".format(region))) + for region in Session().get_available_regions("ec2", partition_name="aws-cn"): + regions.append(Region(region, "ec2.{}.amazonaws.com.cn".format(region))) zones = { "ap-south-1": [ @@ -1536,6 +1542,11 @@ class RegionsAndZonesBackend(object): zone_id="apne1-az2", ), ], + "ap-east-1": [ + Zone(region_name="ap-east-1", name="ap-east-1a", zone_id="ape1-az1"), + Zone(region_name="ap-east-1", name="ap-east-1b", zone_id="ape1-az2"), + Zone(region_name="ap-east-1", name="ap-east-1c", zone_id="ape1-az3"), + ], "sa-east-1": [ Zone(region_name="sa-east-1", name="sa-east-1a", zone_id="sae1-az1"), Zone(region_name="sa-east-1", name="sa-east-1c", zone_id="sae1-az3"), @@ -1605,10 +1616,32 @@ class RegionsAndZonesBackend(object): Zone(region_name="us-west-2", name="us-west-2b", zone_id="usw2-az1"), Zone(region_name="us-west-2", name="us-west-2c", zone_id="usw2-az3"), ], + "me-south-1": [ + Zone(region_name="me-south-1", name="me-south-1a", zone_id="mes1-az1"), + Zone(region_name="me-south-1", name="me-south-1b", zone_id="mes1-az2"), + Zone(region_name="me-south-1", name="me-south-1c", zone_id="mes1-az3"), + ], "cn-north-1": [ Zone(region_name="cn-north-1", name="cn-north-1a", zone_id="cnn1-az1"), Zone(region_name="cn-north-1", name="cn-north-1b", zone_id="cnn1-az2"), ], + "cn-northwest-1": [ + Zone( + region_name="cn-northwest-1", + name="cn-northwest-1a", + zone_id="cnnw1-az1", + ), + Zone( + region_name="cn-northwest-1", + name="cn-northwest-1b", + zone_id="cnnw1-az2", + ), + Zone( + region_name="cn-northwest-1", + name="cn-northwest-1c", + zone_id="cnnw1-az3", + ), + ], "us-gov-west-1": [ Zone( region_name="us-gov-west-1", name="us-gov-west-1a", zone_id="usgw1-az1" @@ -1620,6 +1653,17 @@ class RegionsAndZonesBackend(object): region_name="us-gov-west-1", name="us-gov-west-1c", zone_id="usgw1-az3" ), ], + "us-gov-east-1": [ + Zone( + region_name="us-gov-east-1", name="us-gov-east-1a", zone_id="usge1-az1" + ), + Zone( + region_name="us-gov-east-1", name="us-gov-east-1b", zone_id="usge1-az2" + ), + Zone( + region_name="us-gov-east-1", name="us-gov-east-1c", zone_id="usge1-az3" + ), + ], } def describe_regions(self, region_names=[]): @@ -2449,6 +2493,7 @@ class VPC(TaggedEC2Resource): self.is_default = "true" if is_default else "false" self.enable_dns_support = "true" self.classic_link_enabled = "false" + self.classic_link_dns_supported = "false" # This attribute is set to 'true' only for default VPCs # or VPCs created using the wizard of the VPC console self.enable_dns_hostnames = "true" if is_default else "false" @@ -3306,6 +3351,7 @@ class Route(object): local=False, gateway=None, instance=None, + nat_gateway=None, interface=None, vpc_pcx=None, ): @@ -3315,6 +3361,7 @@ class Route(object): self.local = local self.gateway = gateway self.instance = instance + self.nat_gateway = nat_gateway self.interface = interface self.vpc_pcx = vpc_pcx @@ -3327,6 +3374,7 @@ class Route(object): gateway_id = properties.get("GatewayId") instance_id = properties.get("InstanceId") interface_id = properties.get("NetworkInterfaceId") + nat_gateway_id = properties.get("NatGatewayId") pcx_id = properties.get("VpcPeeringConnectionId") route_table_id = properties["RouteTableId"] @@ -3336,6 +3384,7 @@ class Route(object): destination_cidr_block=properties.get("DestinationCidrBlock"), gateway_id=gateway_id, instance_id=instance_id, + nat_gateway_id=nat_gateway_id, interface_id=interface_id, vpc_peering_connection_id=pcx_id, ) @@ -3353,6 +3402,7 @@ class RouteBackend(object): local=False, gateway_id=None, instance_id=None, + nat_gateway_id=None, interface_id=None, vpc_peering_connection_id=None, ): @@ -3373,12 +3423,17 @@ class RouteBackend(object): except ValueError: raise InvalidDestinationCIDRBlockParameterError(destination_cidr_block) + nat_gateway = None + if nat_gateway_id is not None: + nat_gateway = self.nat_gateways.get(nat_gateway_id) + route = Route( route_table, destination_cidr_block, local=local, gateway=gateway, instance=self.get_instance(instance_id) if instance_id else None, + nat_gateway=nat_gateway, interface=None, vpc_pcx=self.get_vpc_peering_connection(vpc_peering_connection_id) if vpc_peering_connection_id @@ -4827,7 +4882,35 @@ class NatGatewayBackend(object): super(NatGatewayBackend, self).__init__() def get_all_nat_gateways(self, filters): - return self.nat_gateways.values() + nat_gateways = self.nat_gateways.values() + + if filters is not None: + if filters.get("nat-gateway-id") is not None: + nat_gateways = [ + nat_gateway + for nat_gateway in nat_gateways + if nat_gateway.id in filters["nat-gateway-id"] + ] + if filters.get("vpc-id") is not None: + nat_gateways = [ + nat_gateway + for nat_gateway in nat_gateways + if nat_gateway.vpc_id in filters["vpc-id"] + ] + if filters.get("subnet-id") is not None: + nat_gateways = [ + nat_gateway + for nat_gateway in nat_gateways + if nat_gateway.subnet_id in filters["subnet-id"] + ] + if filters.get("state") is not None: + nat_gateways = [ + nat_gateway + for nat_gateway in nat_gateways + if nat_gateway.state in filters["state"] + ] + + return nat_gateways def create_nat_gateway(self, subnet_id, allocation_id): nat_gateway = NatGateway(self, subnet_id, allocation_id) diff --git a/moto/ec2/responses/instances.py b/moto/ec2/responses/instances.py index 4b7a20a17..b9e572d29 100644 --- a/moto/ec2/responses/instances.py +++ b/moto/ec2/responses/instances.py @@ -6,6 +6,7 @@ from moto.core.responses import BaseResponse from moto.core.utils import camelcase_to_underscores from moto.ec2.utils import filters_from_querystring, dict_from_querystring from moto.elbv2 import elbv2_backends +from moto.core import ACCOUNT_ID class InstanceResponse(BaseResponse): @@ -246,10 +247,13 @@ class InstanceResponse(BaseResponse): return EC2_MODIFY_INSTANCE_ATTRIBUTE -EC2_RUN_INSTANCES = """ +EC2_RUN_INSTANCES = ( + """ 59dbff89-35bd-4eac-99ed-be587EXAMPLE {{ reservation.id }} - 123456789012 + """ + + ACCOUNT_ID + + """ sg-245f6a01 @@ -331,7 +335,9 @@ EC2_RUN_INSTANCES = """ in-use 1b:2b:3c:4d:5e:6f {{ nic.private_ip_address }} @@ -354,7 +360,9 @@ EC2_RUN_INSTANCES = """ {% endif %} @@ -364,7 +372,9 @@ EC2_RUN_INSTANCES = """ {% endif %} @@ -376,14 +386,18 @@ EC2_RUN_INSTANCES = """ +EC2_DESCRIBE_INSTANCES = ( + """ fdcdcab1-ae5c-489e-9c33-4637c5dda355 {% for reservation in reservations %} {{ reservation.id }} - 123456789012 + """ + + ACCOUNT_ID + + """ {% for group in reservation.dynamic_group_list %} @@ -476,7 +490,9 @@ EC2_DESCRIBE_INSTANCES = """ {% if instance.get_tags() %} {% for tag in instance.get_tags() %} @@ -499,7 +515,9 @@ EC2_DESCRIBE_INSTANCES = """ in-use 1b:2b:3c:4d:5e:6f {{ nic.private_ip_address }} @@ -526,7 +544,9 @@ EC2_DESCRIBE_INSTANCES = """ {% endif %} @@ -536,7 +556,9 @@ EC2_DESCRIBE_INSTANCES = """ {% endif %} @@ -554,6 +576,7 @@ EC2_DESCRIBE_INSTANCES = """ diff --git a/moto/ec2/responses/route_tables.py b/moto/ec2/responses/route_tables.py index ef796e401..b5d65f831 100644 --- a/moto/ec2/responses/route_tables.py +++ b/moto/ec2/responses/route_tables.py @@ -18,6 +18,7 @@ class RouteTables(BaseResponse): destination_cidr_block = self._get_param("DestinationCidrBlock") gateway_id = self._get_param("GatewayId") instance_id = self._get_param("InstanceId") + nat_gateway_id = self._get_param("NatGatewayId") interface_id = self._get_param("NetworkInterfaceId") pcx_id = self._get_param("VpcPeeringConnectionId") @@ -26,6 +27,7 @@ class RouteTables(BaseResponse): destination_cidr_block, gateway_id=gateway_id, instance_id=instance_id, + nat_gateway_id=nat_gateway_id, interface_id=interface_id, vpc_peering_connection_id=pcx_id, ) @@ -173,6 +175,10 @@ DESCRIBE_ROUTE_TABLES_RESPONSE = """ CreateRoute blackhole {% endif %} + {% if route.nat_gateway %} + {{ route.nat_gateway.id }} + active + {% endif %} {% endfor %} diff --git a/moto/ec2/responses/security_groups.py b/moto/ec2/responses/security_groups.py index d2cfff977..6f2926f61 100644 --- a/moto/ec2/responses/security_groups.py +++ b/moto/ec2/responses/security_groups.py @@ -2,6 +2,7 @@ from __future__ import unicode_literals from moto.core.responses import BaseResponse from moto.ec2.utils import filters_from_querystring +from moto.core import ACCOUNT_ID def try_parse_int(value, default=None): @@ -171,12 +172,15 @@ DELETE_GROUP_RESPONSE = """ +DESCRIBE_SECURITY_GROUPS_RESPONSE = ( + """ 59dbff89-35bd-4eac-99ed-be587EXAMPLE {% for group in groups %} - 123456789012 + """ + + ACCOUNT_ID + + """ {{ group.id }} {{ group.name }} {{ group.description }} @@ -196,7 +200,9 @@ DESCRIBE_SECURITY_GROUPS_RESPONSE = """ {{ source_group.id }} {{ source_group.name }} @@ -225,7 +231,9 @@ DESCRIBE_SECURITY_GROUPS_RESPONSE = """ {{ source_group.id }} {{ source_group.name }} @@ -255,6 +263,7 @@ DESCRIBE_SECURITY_GROUPS_RESPONSE = """ 59dbff89-35bd-4eac-99ed-be587EXAMPLE diff --git a/moto/ec2/responses/vpc_peering_connections.py b/moto/ec2/responses/vpc_peering_connections.py index ff792a6cc..3bf86af8a 100644 --- a/moto/ec2/responses/vpc_peering_connections.py +++ b/moto/ec2/responses/vpc_peering_connections.py @@ -1,5 +1,6 @@ from __future__ import unicode_literals from moto.core.responses import BaseResponse +from moto.core import ACCOUNT_ID class VPCPeeringConnections(BaseResponse): @@ -40,7 +41,8 @@ class VPCPeeringConnections(BaseResponse): return template.render() -CREATE_VPC_PEERING_CONNECTION_RESPONSE = """ +CREATE_VPC_PEERING_CONNECTION_RESPONSE = ( + """ 7a62c49f-347e-4fc4-9331-6e8eEXAMPLE @@ -56,7 +58,9 @@ CREATE_VPC_PEERING_CONNECTION_RESPONSE = """ - 123456789012 + """ + + ACCOUNT_ID + + """ {{ vpc_pcx.peer_vpc.id }} @@ -68,8 +72,10 @@ CREATE_VPC_PEERING_CONNECTION_RESPONSE = """ """ +) -DESCRIBE_VPC_PEERING_CONNECTIONS_RESPONSE = """ +DESCRIBE_VPC_PEERING_CONNECTIONS_RESPONSE = ( + """ 7a62c49f-347e-4fc4-9331-6e8eEXAMPLE @@ -82,7 +88,9 @@ DESCRIBE_VPC_PEERING_CONNECTIONS_RESPONSE = """ {{ vpc_pcx.vpc.cidr_block }} - 123456789012 + """ + + ACCOUNT_ID + + """ {{ vpc_pcx.peer_vpc.id }} {{ vpc_pcx.peer_vpc.cidr_block }} @@ -101,6 +109,7 @@ DESCRIBE_VPC_PEERING_CONNECTIONS_RESPONSE = """ """ +) DELETE_VPC_PEERING_CONNECTION_RESPONSE = """ @@ -109,7 +118,8 @@ DELETE_VPC_PEERING_CONNECTION_RESPONSE = """ """ -ACCEPT_VPC_PEERING_CONNECTION_RESPONSE = """ +ACCEPT_VPC_PEERING_CONNECTION_RESPONSE = ( + """ 7a62c49f-347e-4fc4-9331-6e8eEXAMPLE @@ -120,7 +130,9 @@ ACCEPT_VPC_PEERING_CONNECTION_RESPONSE = """ {{ vpc_pcx.vpc.cidr_block }} - 123456789012 + """ + + ACCOUNT_ID + + """ {{ vpc_pcx.peer_vpc.id }} {{ vpc_pcx.peer_vpc.cidr_block }} @@ -137,6 +149,7 @@ ACCEPT_VPC_PEERING_CONNECTION_RESPONSE = """ """ +) REJECT_VPC_PEERING_CONNECTION_RESPONSE = """ diff --git a/moto/ec2/urls.py b/moto/ec2/urls.py index b83a9e950..4d85b2f56 100644 --- a/moto/ec2/urls.py +++ b/moto/ec2/urls.py @@ -2,6 +2,6 @@ from __future__ import unicode_literals from .responses import EC2Response -url_bases = ["https?://ec2.(.+).amazonaws.com(|.cn)"] +url_bases = ["https?://ec2\.(.+)\.amazonaws\.com(|\.cn)"] url_paths = {"{0}/": EC2Response.dispatch} diff --git a/moto/ec2_instance_connect/__init__.py b/moto/ec2_instance_connect/__init__.py new file mode 100644 index 000000000..c20d59cfa --- /dev/null +++ b/moto/ec2_instance_connect/__init__.py @@ -0,0 +1,4 @@ +from ..core.models import base_decorator +from .models import ec2_instance_connect_backends + +mock_ec2_instance_connect = base_decorator(ec2_instance_connect_backends) diff --git a/moto/ec2_instance_connect/models.py b/moto/ec2_instance_connect/models.py new file mode 100644 index 000000000..cc8cc3f33 --- /dev/null +++ b/moto/ec2_instance_connect/models.py @@ -0,0 +1,11 @@ +import boto +from moto.core import BaseBackend + + +class Ec2InstanceConnectBackend(BaseBackend): + pass + + +ec2_instance_connect_backends = {} +for region in boto.ec2.regions(): + ec2_instance_connect_backends[region.name] = Ec2InstanceConnectBackend() diff --git a/moto/ec2_instance_connect/responses.py b/moto/ec2_instance_connect/responses.py new file mode 100644 index 000000000..462f1fddc --- /dev/null +++ b/moto/ec2_instance_connect/responses.py @@ -0,0 +1,9 @@ +import json +from moto.core.responses import BaseResponse + + +class Ec2InstanceConnectResponse(BaseResponse): + def send_ssh_public_key(self): + return json.dumps( + {"RequestId": "example-2a47-4c91-9700-e37e85162cb6", "Success": True} + ) diff --git a/moto/ec2_instance_connect/urls.py b/moto/ec2_instance_connect/urls.py new file mode 100644 index 000000000..e7078264f --- /dev/null +++ b/moto/ec2_instance_connect/urls.py @@ -0,0 +1,6 @@ +from __future__ import unicode_literals +from .responses import Ec2InstanceConnectResponse + +url_bases = ["https?://ec2-instance-connect\.(.+)\.amazonaws\.com"] + +url_paths = {"{0}/$": Ec2InstanceConnectResponse.dispatch} diff --git a/moto/ecs/models.py b/moto/ecs/models.py index c9dc998ee..845bdf650 100644 --- a/moto/ecs/models.py +++ b/moto/ecs/models.py @@ -3,9 +3,10 @@ import re import uuid from datetime import datetime from random import random, randint -import boto3 import pytz +from boto3 import Session + from moto.core.exceptions import JsonRESTError from moto.core import BaseBackend, BaseModel from moto.core.utils import unix_time @@ -1302,7 +1303,10 @@ class EC2ContainerServiceBackend(BaseBackend): raise NotImplementedError() -available_regions = boto3.session.Session().get_available_regions("ecs") -ecs_backends = { - region: EC2ContainerServiceBackend(region) for region in available_regions -} +ecs_backends = {} +for region in Session().get_available_regions("ecs"): + ecs_backends[region] = EC2ContainerServiceBackend(region) +for region in Session().get_available_regions("ecs", partition_name="aws-us-gov"): + ecs_backends[region] = EC2ContainerServiceBackend(region) +for region in Session().get_available_regions("ecs", partition_name="aws-cn"): + ecs_backends[region] = EC2ContainerServiceBackend(region) diff --git a/moto/emr/models.py b/moto/emr/models.py index b62ce7932..713b15b9f 100644 --- a/moto/emr/models.py +++ b/moto/emr/models.py @@ -2,8 +2,8 @@ from __future__ import unicode_literals from datetime import datetime from datetime import timedelta -import boto.emr import pytz +from boto3 import Session from dateutil.parser import parse as dtparse from moto.core import BaseBackend, BaseModel from moto.emr.exceptions import EmrError @@ -460,5 +460,9 @@ class ElasticMapReduceBackend(BaseBackend): emr_backends = {} -for region in boto.emr.regions(): - emr_backends[region.name] = ElasticMapReduceBackend(region.name) +for region in Session().get_available_regions("emr"): + emr_backends[region] = ElasticMapReduceBackend(region) +for region in Session().get_available_regions("emr", partition_name="aws-us-gov"): + emr_backends[region] = ElasticMapReduceBackend(region) +for region in Session().get_available_regions("emr", partition_name="aws-cn"): + emr_backends[region] = ElasticMapReduceBackend(region) diff --git a/moto/events/models.py b/moto/events/models.py index 0298c7c69..548d41393 100644 --- a/moto/events/models.py +++ b/moto/events/models.py @@ -1,7 +1,7 @@ import os import re import json -import boto3 +from boto3 import Session from moto.core.exceptions import JsonRESTError from moto.core import BaseBackend, BaseModel @@ -362,5 +362,10 @@ class EventsBackend(BaseBackend): self.event_buses.pop(name, None) -available_regions = boto3.session.Session().get_available_regions("events") -events_backends = {region: EventsBackend(region) for region in available_regions} +events_backends = {} +for region in Session().get_available_regions("events"): + events_backends[region] = EventsBackend(region) +for region in Session().get_available_regions("events", partition_name="aws-us-gov"): + events_backends[region] = EventsBackend(region) +for region in Session().get_available_regions("events", partition_name="aws-cn"): + events_backends[region] = EventsBackend(region) diff --git a/moto/glacier/models.py b/moto/glacier/models.py index 6a3fc074d..9e91ea3a5 100644 --- a/moto/glacier/models.py +++ b/moto/glacier/models.py @@ -4,8 +4,8 @@ import hashlib import datetime +from boto3 import Session -import boto.glacier from moto.core import BaseBackend, BaseModel from .utils import get_job_id @@ -221,5 +221,9 @@ class GlacierBackend(BaseBackend): glacier_backends = {} -for region in boto.glacier.regions(): - glacier_backends[region.name] = GlacierBackend(region) +for region in Session().get_available_regions("glacier"): + glacier_backends[region] = GlacierBackend(region) +for region in Session().get_available_regions("glacier", partition_name="aws-us-gov"): + glacier_backends[region] = GlacierBackend(region) +for region in Session().get_available_regions("glacier", partition_name="aws-cn"): + glacier_backends[region] = GlacierBackend(region) diff --git a/moto/iam/models.py b/moto/iam/models.py index c67d5b365..18b3a7a6f 100644 --- a/moto/iam/models.py +++ b/moto/iam/models.py @@ -14,7 +14,7 @@ from cryptography.hazmat.backends import default_backend from six.moves.urllib.parse import urlparse from moto.core.exceptions import RESTError -from moto.core import BaseBackend, BaseModel +from moto.core import BaseBackend, BaseModel, ACCOUNT_ID from moto.core.utils import ( iso_8601_datetime_without_milliseconds, iso_8601_datetime_with_milliseconds, @@ -45,8 +45,6 @@ from .utils import ( random_policy_id, ) -ACCOUNT_ID = 123456789012 - class MFADevice(object): """MFA Device class.""" @@ -309,6 +307,7 @@ class Role(BaseModel): permissions_boundary, description, tags, + max_session_duration, ): self.id = role_id self.name = name @@ -320,6 +319,7 @@ class Role(BaseModel): self.tags = tags self.description = description self.permissions_boundary = permissions_boundary + self.max_session_duration = max_session_duration @property def created_iso_8601(self): @@ -338,6 +338,7 @@ class Role(BaseModel): permissions_boundary=properties.get("PermissionsBoundary", ""), description=properties.get("Description", ""), tags=properties.get("Tags", {}), + max_session_duration=properties.get("MaxSessionDuration", 3600), ) policies = properties.get("Policies", []) @@ -542,7 +543,7 @@ class Group(BaseModel): class User(BaseModel): - def __init__(self, name, path=None): + def __init__(self, name, path=None, tags=None): self.name = name self.id = random_resource_id() self.path = path if path else "/" @@ -555,6 +556,7 @@ class User(BaseModel): self.password = None self.password_reset_required = False self.signing_certificates = {} + self.tags = tags @property def arn(self): @@ -938,9 +940,10 @@ class IAMBackend(BaseBackend): role.description = role_description return role - def update_role(self, role_name, role_description): + def update_role(self, role_name, role_description, max_session_duration): role = self.get_role(role_name) role.description = role_description + role.max_session_duration = max_session_duration return role def detach_role_policy(self, policy_arn, role_name): @@ -1059,6 +1062,7 @@ class IAMBackend(BaseBackend): permissions_boundary, description, tags, + max_session_duration, ): role_id = random_resource_id() if permissions_boundary and not self.policy_arn_regex.match( @@ -1084,6 +1088,7 @@ class IAMBackend(BaseBackend): permissions_boundary, description, clean_tags, + max_session_duration, ) self.roles[role_id] = role return role @@ -1417,13 +1422,13 @@ class IAMBackend(BaseBackend): "The group with name {0} cannot be found.".format(group_name) ) - def create_user(self, user_name, path="/"): + def create_user(self, user_name, path="/", tags=None): if user_name in self.users: raise IAMConflictException( "EntityAlreadyExists", "User {0} already exists".format(user_name) ) - user = User(user_name, path) + user = User(user_name, path, tags) self.users[user_name] = user return user @@ -1579,6 +1584,10 @@ class IAMBackend(BaseBackend): user = self.get_user(user_name) return user.policies.keys() + def list_user_tags(self, user_name): + user = self.get_user(user_name) + return user.tags + def put_user_policy(self, user_name, policy_name, policy_json): user = self.get_user(user_name) diff --git a/moto/iam/responses.py b/moto/iam/responses.py index ea14bef0f..06561d4c4 100644 --- a/moto/iam/responses.py +++ b/moto/iam/responses.py @@ -182,6 +182,7 @@ class IamResponse(BaseResponse): permissions_boundary = self._get_param("PermissionsBoundary") description = self._get_param("Description") tags = self._get_multi_param("Tags.member") + max_session_duration = self._get_param("MaxSessionDuration", 3600) role = iam_backend.create_role( role_name, @@ -190,6 +191,7 @@ class IamResponse(BaseResponse): permissions_boundary, description, tags, + max_session_duration, ) template = self.response_template(CREATE_ROLE_TEMPLATE) return template.render(role=role) @@ -258,7 +260,8 @@ class IamResponse(BaseResponse): def update_role(self): role_name = self._get_param("RoleName") description = self._get_param("Description") - role = iam_backend.update_role(role_name, description) + max_session_duration = self._get_param("MaxSessionDuration", 3600) + role = iam_backend.update_role(role_name, description, max_session_duration) template = self.response_template(UPDATE_ROLE_TEMPLATE) return template.render(role=role) @@ -437,8 +440,8 @@ class IamResponse(BaseResponse): def create_user(self): user_name = self._get_param("UserName") path = self._get_param("Path") - - user = iam_backend.create_user(user_name, path) + tags = self._get_multi_param("Tags.member") + user = iam_backend.create_user(user_name, path, tags) template = self.response_template(USER_TEMPLATE) return template.render(action="Create", user=user) @@ -535,6 +538,12 @@ class IamResponse(BaseResponse): template = self.response_template(LIST_USER_POLICIES_TEMPLATE) return template.render(policies=policies) + def list_user_tags(self): + user_name = self._get_param("UserName") + tags = iam_backend.list_user_tags(user_name) + template = self.response_template(LIST_USER_TAGS_TEMPLATE) + return template.render(user_tags=tags or []) + def put_user_policy(self): user_name = self._get_param("UserName") policy_name = self._get_param("PolicyName") @@ -1189,9 +1198,12 @@ CREATE_ROLE_TEMPLATE = """ """ +LIST_USER_TAGS_TEMPLATE = """ + + + {% for tag in user_tags %} + + {{ tag.Key }} + {{ tag.Value }} + + {% endfor %} + + false + + + 7a62c49f-347e-4fc4-9331-6e8eEXAMPLE + +""" + CREATE_ACCESS_KEY_TEMPLATE = """ diff --git a/moto/iot/models.py b/moto/iot/models.py index 74a3e992c..d59d7533c 100644 --- a/moto/iot/models.py +++ b/moto/iot/models.py @@ -9,7 +9,7 @@ import uuid from collections import OrderedDict from datetime import datetime -import boto3 +from boto3 import Session from moto.core import BaseBackend, BaseModel from .exceptions import ( @@ -825,5 +825,10 @@ class IoTBackend(BaseBackend): return self.jobs[job_id] -available_regions = boto3.session.Session().get_available_regions("iot") -iot_backends = {region: IoTBackend(region) for region in available_regions} +iot_backends = {} +for region in Session().get_available_regions("iot"): + iot_backends[region] = IoTBackend(region) +for region in Session().get_available_regions("iot", partition_name="aws-us-gov"): + iot_backends[region] = IoTBackend(region) +for region in Session().get_available_regions("iot", partition_name="aws-cn"): + iot_backends[region] = IoTBackend(region) diff --git a/moto/iotdata/models.py b/moto/iotdata/models.py index e534e1d1f..41b69bc7f 100644 --- a/moto/iotdata/models.py +++ b/moto/iotdata/models.py @@ -1,8 +1,9 @@ from __future__ import unicode_literals import json import time -import boto3 import jsondiff +from boto3 import Session + from moto.core import BaseBackend, BaseModel from moto.iot import iot_backends from .exceptions import ( @@ -205,5 +206,10 @@ class IoTDataPlaneBackend(BaseBackend): return None -available_regions = boto3.session.Session().get_available_regions("iot-data") -iotdata_backends = {region: IoTDataPlaneBackend(region) for region in available_regions} +iotdata_backends = {} +for region in Session().get_available_regions("iot-data"): + iotdata_backends[region] = IoTDataPlaneBackend(region) +for region in Session().get_available_regions("iot-data", partition_name="aws-us-gov"): + iotdata_backends[region] = IoTDataPlaneBackend(region) +for region in Session().get_available_regions("iot-data", partition_name="aws-cn"): + iotdata_backends[region] = IoTDataPlaneBackend(region) diff --git a/moto/kinesis/exceptions.py b/moto/kinesis/exceptions.py index 8c950c355..1f25d6720 100644 --- a/moto/kinesis/exceptions.py +++ b/moto/kinesis/exceptions.py @@ -2,6 +2,7 @@ from __future__ import unicode_literals import json from werkzeug.exceptions import BadRequest +from moto.core import ACCOUNT_ID class ResourceNotFoundError(BadRequest): @@ -23,14 +24,14 @@ class ResourceInUseError(BadRequest): class StreamNotFoundError(ResourceNotFoundError): def __init__(self, stream_name): super(StreamNotFoundError, self).__init__( - "Stream {0} under account 123456789012 not found.".format(stream_name) + "Stream {0} under account {1} not found.".format(stream_name, ACCOUNT_ID) ) class ShardNotFoundError(ResourceNotFoundError): def __init__(self, shard_id): super(ShardNotFoundError, self).__init__( - "Shard {0} under account 123456789012 not found.".format(shard_id) + "Shard {0} under account {1} not found.".format(shard_id, ACCOUNT_ID) ) diff --git a/moto/kinesis/models.py b/moto/kinesis/models.py index 38a622841..ec9655bfa 100644 --- a/moto/kinesis/models.py +++ b/moto/kinesis/models.py @@ -2,7 +2,6 @@ from __future__ import unicode_literals import datetime import time -import boto.kinesis import re import six import itertools @@ -10,9 +9,12 @@ import itertools from operator import attrgetter from hashlib import md5 +from boto3 import Session + from moto.compat import OrderedDict from moto.core import BaseBackend, BaseModel from moto.core.utils import unix_time +from moto.core import ACCOUNT_ID from .exceptions import ( StreamNotFoundError, ShardNotFoundError, @@ -133,7 +135,7 @@ class Stream(BaseModel): self.shard_count = shard_count self.creation_datetime = datetime.datetime.now() self.region = region - self.account_number = "123456789012" + self.account_number = ACCOUNT_ID self.shards = {} self.tags = {} self.status = "ACTIVE" @@ -259,8 +261,8 @@ class DeliveryStream(BaseModel): @property def arn(self): - return "arn:aws:firehose:us-east-1:123456789012:deliverystream/{0}".format( - self.name + return "arn:aws:firehose:us-east-1:{1}:deliverystream/{0}".format( + self.name, ACCOUNT_ID ) def destinations_to_dict(self): @@ -529,5 +531,9 @@ class KinesisBackend(BaseBackend): kinesis_backends = {} -for region in boto.kinesis.regions(): - kinesis_backends[region.name] = KinesisBackend() +for region in Session().get_available_regions("kinesis"): + kinesis_backends[region] = KinesisBackend() +for region in Session().get_available_regions("kinesis", partition_name="aws-us-gov"): + kinesis_backends[region] = KinesisBackend() +for region in Session().get_available_regions("kinesis", partition_name="aws-cn"): + kinesis_backends[region] = KinesisBackend() diff --git a/moto/kms/models.py b/moto/kms/models.py index 9d7739779..22f0039b2 100644 --- a/moto/kms/models.py +++ b/moto/kms/models.py @@ -4,7 +4,7 @@ import os from collections import defaultdict from datetime import datetime, timedelta -import boto.kms +from boto3 import Session from moto.core import BaseBackend, BaseModel from moto.core.utils import iso_8601_datetime_without_milliseconds @@ -284,5 +284,9 @@ class KmsBackend(BaseBackend): kms_backends = {} -for region in boto.kms.regions(): - kms_backends[region.name] = KmsBackend() +for region in Session().get_available_regions("kms"): + kms_backends[region] = KmsBackend() +for region in Session().get_available_regions("kms", partition_name="aws-us-gov"): + kms_backends[region] = KmsBackend() +for region in Session().get_available_regions("kms", partition_name="aws-cn"): + kms_backends[region] = KmsBackend() diff --git a/moto/logs/models.py b/moto/logs/models.py index d0639524e..7448319db 100644 --- a/moto/logs/models.py +++ b/moto/logs/models.py @@ -1,5 +1,6 @@ +from boto3 import Session + from moto.core import BaseBackend -import boto.logs from moto.core.utils import unix_time_millis from .exceptions import ( ResourceNotFoundException, @@ -558,6 +559,10 @@ class LogsBackend(BaseBackend): log_group.untag(tags) -logs_backends = { - region.name: LogsBackend(region.name) for region in boto.logs.regions() -} +logs_backends = {} +for region in Session().get_available_regions("logs"): + logs_backends[region] = LogsBackend(region) +for region in Session().get_available_regions("logs", partition_name="aws-us-gov"): + logs_backends[region] = LogsBackend(region) +for region in Session().get_available_regions("logs", partition_name="aws-cn"): + logs_backends[region] = LogsBackend(region) diff --git a/moto/opsworks/models.py b/moto/opsworks/models.py index 336bbde14..96d918cc9 100644 --- a/moto/opsworks/models.py +++ b/moto/opsworks/models.py @@ -1,6 +1,7 @@ from __future__ import unicode_literals from moto.core import BaseBackend, BaseModel from moto.ec2 import ec2_backends +from moto.core import ACCOUNT_ID import uuid import datetime from random import choice @@ -367,7 +368,7 @@ class Stack(BaseModel): self.id = "{0}".format(uuid.uuid4()) self.layers = [] self.apps = [] - self.account_number = "123456789012" + self.account_number = ACCOUNT_ID self.created_at = datetime.datetime.utcnow() def __eq__(self, other): diff --git a/moto/organizations/utils.py b/moto/organizations/utils.py index dacd58502..e71357ce6 100644 --- a/moto/organizations/utils.py +++ b/moto/organizations/utils.py @@ -2,8 +2,10 @@ from __future__ import unicode_literals import random import string +from moto.core import ACCOUNT_ID -MASTER_ACCOUNT_ID = "123456789012" + +MASTER_ACCOUNT_ID = ACCOUNT_ID MASTER_ACCOUNT_EMAIL = "master@example.com" DEFAULT_POLICY_ID = "p-FullAWSAccess" ORGANIZATION_ARN_FORMAT = "arn:aws:organizations::{0}:organization/{1}" diff --git a/moto/polly/models.py b/moto/polly/models.py index 3be5b7a0b..cf4c8ab03 100644 --- a/moto/polly/models.py +++ b/moto/polly/models.py @@ -2,13 +2,14 @@ from __future__ import unicode_literals from xml.etree import ElementTree as ET import datetime -import boto3 +from boto3 import Session + from moto.core import BaseBackend, BaseModel from .resources import VOICE_DATA from .utils import make_arn_for_lexicon -DEFAULT_ACCOUNT_ID = 123456789012 +from moto.core import ACCOUNT_ID as DEFAULT_ACCOUNT_ID class Lexicon(BaseModel): @@ -113,7 +114,10 @@ class PollyBackend(BaseBackend): self._lexicons[name] = lexicon -available_regions = boto3.session.Session().get_available_regions("polly") -polly_backends = { - region: PollyBackend(region_name=region) for region in available_regions -} +polly_backends = {} +for region in Session().get_available_regions("polly"): + polly_backends[region] = PollyBackend(region) +for region in Session().get_available_regions("polly", partition_name="aws-us-gov"): + polly_backends[region] = PollyBackend(region) +for region in Session().get_available_regions("polly", partition_name="aws-cn"): + polly_backends[region] = PollyBackend(region) diff --git a/moto/rds2/models.py b/moto/rds2/models.py index 686d22ccf..e648765b7 100644 --- a/moto/rds2/models.py +++ b/moto/rds2/models.py @@ -5,7 +5,7 @@ import datetime import os from collections import defaultdict -import boto.rds2 +from boto3 import Session from jinja2 import Template from re import compile as re_compile from moto.cloudformation.exceptions import UnformattedGetAttTemplateException @@ -1501,6 +1501,10 @@ class DBParameterGroup(object): return db_parameter_group -rds2_backends = dict( - (region.name, RDS2Backend(region.name)) for region in boto.rds2.regions() -) +rds2_backends = {} +for region in Session().get_available_regions("rds"): + rds2_backends[region] = RDS2Backend(region) +for region in Session().get_available_regions("rds", partition_name="aws-us-gov"): + rds2_backends[region] = RDS2Backend(region) +for region in Session().get_available_regions("rds", partition_name="aws-cn"): + rds2_backends[region] = RDS2Backend(region) diff --git a/moto/redshift/models.py b/moto/redshift/models.py index 3eac565f8..17840fb86 100644 --- a/moto/redshift/models.py +++ b/moto/redshift/models.py @@ -3,7 +3,7 @@ from __future__ import unicode_literals import copy import datetime -import boto.redshift +from boto3 import Session from botocore.exceptions import ClientError from moto.compat import OrderedDict from moto.core import BaseBackend, BaseModel @@ -27,7 +27,7 @@ from .exceptions import ( ) -ACCOUNT_ID = 123456789012 +from moto.core import ACCOUNT_ID class TaggableResourceMixin(object): @@ -897,7 +897,9 @@ class RedshiftBackend(BaseBackend): redshift_backends = {} -for region in boto.redshift.regions(): - redshift_backends[region.name] = RedshiftBackend( - ec2_backends[region.name], region.name - ) +for region in Session().get_available_regions("redshift"): + redshift_backends[region] = RedshiftBackend(ec2_backends[region], region) +for region in Session().get_available_regions("redshift", partition_name="aws-us-gov"): + redshift_backends[region] = RedshiftBackend(ec2_backends[region], region) +for region in Session().get_available_regions("redshift", partition_name="aws-cn"): + redshift_backends[region] = RedshiftBackend(ec2_backends[region], region) diff --git a/moto/resourcegroups/models.py b/moto/resourcegroups/models.py index 5dd54d197..4dd96408a 100644 --- a/moto/resourcegroups/models.py +++ b/moto/resourcegroups/models.py @@ -1,11 +1,13 @@ from __future__ import unicode_literals from builtins import str -import boto3 import json import re +from boto3 import Session + from moto.core import BaseBackend, BaseModel +from moto.core import ACCOUNT_ID from .exceptions import BadRequestException @@ -23,8 +25,8 @@ class FakeResourceGroup(BaseModel): if self._validate_tags(value=tags): self._tags = tags self._raise_errors() - self.arn = "arn:aws:resource-groups:us-west-1:123456789012:{name}".format( - name=name + self.arn = "arn:aws:resource-groups:us-west-1:{AccountId}:{name}".format( + name=name, AccountId=ACCOUNT_ID ) @staticmethod @@ -349,7 +351,14 @@ class ResourceGroupsBackend(BaseBackend): return self.groups.by_name[group_name] -available_regions = boto3.session.Session().get_available_regions("resource-groups") -resourcegroups_backends = { - region: ResourceGroupsBackend(region_name=region) for region in available_regions -} +resourcegroups_backends = {} +for region in Session().get_available_regions("resource-groups"): + resourcegroups_backends[region] = ResourceGroupsBackend(region) +for region in Session().get_available_regions( + "resource-groups", partition_name="aws-us-gov" +): + resourcegroups_backends[region] = ResourceGroupsBackend(region) +for region in Session().get_available_regions( + "resource-groups", partition_name="aws-cn" +): + resourcegroups_backends[region] = ResourceGroupsBackend(region) diff --git a/moto/resourcegroupstaggingapi/models.py b/moto/resourcegroupstaggingapi/models.py index 7b0c03a88..850ab5c04 100644 --- a/moto/resourcegroupstaggingapi/models.py +++ b/moto/resourcegroupstaggingapi/models.py @@ -1,7 +1,8 @@ from __future__ import unicode_literals import uuid -import boto3 import six +from boto3 import Session + from moto.core import BaseBackend from moto.core.exceptions import RESTError @@ -636,9 +637,14 @@ class ResourceGroupsTaggingAPIBackend(BaseBackend): # return failed_resources_map -available_regions = boto3.session.Session().get_available_regions( - "resourcegroupstaggingapi" -) -resourcegroupstaggingapi_backends = { - region: ResourceGroupsTaggingAPIBackend(region) for region in available_regions -} +resourcegroupstaggingapi_backends = {} +for region in Session().get_available_regions("resourcegroupstaggingapi"): + resourcegroupstaggingapi_backends[region] = ResourceGroupsTaggingAPIBackend(region) +for region in Session().get_available_regions( + "resourcegroupstaggingapi", partition_name="aws-us-gov" +): + resourcegroupstaggingapi_backends[region] = ResourceGroupsTaggingAPIBackend(region) +for region in Session().get_available_regions( + "resourcegroupstaggingapi", partition_name="aws-cn" +): + resourcegroupstaggingapi_backends[region] = ResourceGroupsTaggingAPIBackend(region) diff --git a/moto/s3/exceptions.py b/moto/s3/exceptions.py index c8236398f..1f2ead639 100644 --- a/moto/s3/exceptions.py +++ b/moto/s3/exceptions.py @@ -323,3 +323,27 @@ class BucketSignatureDoesNotMatchError(S3ClientError): *args, **kwargs ) + + +class NoSuchPublicAccessBlockConfiguration(S3ClientError): + code = 404 + + def __init__(self, *args, **kwargs): + super(NoSuchPublicAccessBlockConfiguration, self).__init__( + "NoSuchPublicAccessBlockConfiguration", + "The public access block configuration was not found", + *args, + **kwargs + ) + + +class InvalidPublicAccessBlockConfiguration(S3ClientError): + code = 400 + + def __init__(self, *args, **kwargs): + super(InvalidPublicAccessBlockConfiguration, self).__init__( + "InvalidRequest", + "Must specify at least one configuration.", + *args, + **kwargs + ) diff --git a/moto/s3/models.py b/moto/s3/models.py index 9c8f64242..fe8e908ef 100644 --- a/moto/s3/models.py +++ b/moto/s3/models.py @@ -35,6 +35,8 @@ from .exceptions import ( InvalidTargetBucketForLogging, DuplicateTagKeys, CrossLocationLoggingProhibitted, + NoSuchPublicAccessBlockConfiguration, + InvalidPublicAccessBlockConfiguration, ) from .utils import clean_key_name, _VersionedKeyStore @@ -659,11 +661,8 @@ class Notification(BaseModel): else: data["filter"] = None - data[ - "objectPrefixes" - ] = ( - [] - ) # Not sure why this is a thing since AWS just seems to return this as filters ¯\_(ツ)_/¯ + # Not sure why this is a thing since AWS just seems to return this as filters ¯\_(ツ)_/¯ + data["objectPrefixes"] = [] return data @@ -728,6 +727,38 @@ class NotificationConfiguration(BaseModel): return data +def convert_str_to_bool(item): + """Converts a boolean string to a boolean value""" + if isinstance(item, str): + return item.lower() == "true" + + return False + + +class PublicAccessBlock(BaseModel): + def __init__( + self, + block_public_acls, + ignore_public_acls, + block_public_policy, + restrict_public_buckets, + ): + # The boto XML appears to expect these values to exist as lowercase strings... + self.block_public_acls = block_public_acls or "false" + self.ignore_public_acls = ignore_public_acls or "false" + self.block_public_policy = block_public_policy or "false" + self.restrict_public_buckets = restrict_public_buckets or "false" + + def to_config_dict(self): + # Need to make the string values booleans for Config: + return { + "blockPublicAcls": convert_str_to_bool(self.block_public_acls), + "ignorePublicAcls": convert_str_to_bool(self.ignore_public_acls), + "blockPublicPolicy": convert_str_to_bool(self.block_public_policy), + "restrictPublicBuckets": convert_str_to_bool(self.restrict_public_buckets), + } + + class FakeBucket(BaseModel): def __init__(self, name, region_name): self.name = name @@ -746,6 +777,7 @@ class FakeBucket(BaseModel): self.accelerate_configuration = None self.payer = "BucketOwner" self.creation_date = datetime.datetime.utcnow() + self.public_access_block = None @property def location(self): @@ -1079,13 +1111,16 @@ class FakeBucket(BaseModel): } # Make the supplementary configuration: - # TODO: Implement Public Access Block Support - # This is a dobule-wrapped JSON for some reason... s_config = { "AccessControlList": json.dumps(json.dumps(self.acl.to_config_dict())) } + if self.public_access_block: + s_config["PublicAccessBlockConfiguration"] = json.dumps( + self.public_access_block.to_config_dict() + ) + # Tagging is special: if config_dict["tags"]: s_config["BucketTaggingConfiguration"] = json.dumps( @@ -1221,6 +1256,14 @@ class S3Backend(BaseBackend): bucket = self.get_bucket(bucket_name) return bucket.website_configuration + def get_bucket_public_access_block(self, bucket_name): + bucket = self.get_bucket(bucket_name) + + if not bucket.public_access_block: + raise NoSuchPublicAccessBlockConfiguration() + + return bucket.public_access_block + def set_key( self, bucket_name, key_name, value, storage=None, etag=None, multipart=None ): @@ -1309,6 +1352,10 @@ class S3Backend(BaseBackend): bucket = self.get_bucket(bucket_name) bucket.delete_cors() + def delete_bucket_public_access_block(self, bucket_name): + bucket = self.get_bucket(bucket_name) + bucket.public_access_block = None + def put_bucket_notification_configuration(self, bucket_name, notification_config): bucket = self.get_bucket(bucket_name) bucket.set_notification_configuration(notification_config) @@ -1324,6 +1371,19 @@ class S3Backend(BaseBackend): raise InvalidRequest("PutBucketAccelerateConfiguration") bucket.set_accelerate_configuration(accelerate_configuration) + def put_bucket_public_access_block(self, bucket_name, pub_block_config): + bucket = self.get_bucket(bucket_name) + + if not pub_block_config: + raise InvalidPublicAccessBlockConfiguration() + + bucket.public_access_block = PublicAccessBlock( + pub_block_config.get("BlockPublicAcls"), + pub_block_config.get("IgnorePublicAcls"), + pub_block_config.get("BlockPublicPolicy"), + pub_block_config.get("RestrictPublicBuckets"), + ) + def initiate_multipart(self, bucket_name, key_name, metadata): bucket = self.get_bucket(bucket_name) new_multipart = FakeMultipart(key_name, metadata) diff --git a/moto/s3/responses.py b/moto/s3/responses.py index fd3a7b2db..71f21c8e1 100644 --- a/moto/s3/responses.py +++ b/moto/s3/responses.py @@ -1,10 +1,11 @@ from __future__ import unicode_literals import re +import sys import six -from moto.core.utils import str_to_rfc_1123_datetime +from moto.core.utils import str_to_rfc_1123_datetime, py2_strip_unicode_keys from six.moves.urllib.parse import parse_qs, urlparse, unquote import xmltodict @@ -12,6 +13,7 @@ import xmltodict from moto.packages.httpretty.core import HTTPrettyRequest from moto.core.responses import _TemplateEnvironmentMixin, ActionAuthenticatorMixin from moto.core.utils import path_url +from moto.core import ACCOUNT_ID from moto.s3bucket_path.utils import ( bucket_name_from_url as bucketpath_bucket_name_from_url, @@ -70,6 +72,7 @@ ACTION_MAP = { "notification": "GetBucketNotification", "accelerate": "GetAccelerateConfiguration", "versions": "ListBucketVersions", + "public_access_block": "GetPublicAccessBlock", "DEFAULT": "ListBucket", }, "PUT": { @@ -83,6 +86,7 @@ ACTION_MAP = { "cors": "PutBucketCORS", "notification": "PutBucketNotification", "accelerate": "PutAccelerateConfiguration", + "public_access_block": "PutPublicAccessBlock", "DEFAULT": "CreateBucket", }, "DELETE": { @@ -90,6 +94,7 @@ ACTION_MAP = { "policy": "DeleteBucketPolicy", "tagging": "PutBucketTagging", "cors": "PutBucketCORS", + "public_access_block": "DeletePublicAccessBlock", "DEFAULT": "DeleteBucket", }, }, @@ -399,6 +404,12 @@ class ResponseObject(_TemplateEnvironmentMixin, ActionAuthenticatorMixin): return 200, {}, template.render() template = self.response_template(S3_BUCKET_ACCELERATE) return template.render(bucket=bucket) + elif "publicAccessBlock" in querystring: + public_block_config = self.backend.get_bucket_public_access_block( + bucket_name + ) + template = self.response_template(S3_PUBLIC_ACCESS_BLOCK_CONFIGURATION) + return template.render(public_block_config=public_block_config) elif "versions" in querystring: delimiter = querystring.get("delimiter", [None])[0] @@ -651,6 +662,23 @@ class ResponseObject(_TemplateEnvironmentMixin, ActionAuthenticatorMixin): except Exception as e: raise e + elif "publicAccessBlock" in querystring: + parsed_xml = xmltodict.parse(body) + parsed_xml["PublicAccessBlockConfiguration"].pop("@xmlns", None) + + # If Python 2, fix the unicode strings: + if sys.version_info[0] < 3: + parsed_xml = { + "PublicAccessBlockConfiguration": py2_strip_unicode_keys( + dict(parsed_xml["PublicAccessBlockConfiguration"]) + ) + } + + self.backend.put_bucket_public_access_block( + bucket_name, parsed_xml["PublicAccessBlockConfiguration"] + ) + return "" + else: if body: # us-east-1, the default AWS region behaves a bit differently @@ -706,6 +734,9 @@ class ResponseObject(_TemplateEnvironmentMixin, ActionAuthenticatorMixin): bucket = self.backend.get_bucket(bucket_name) bucket.delete_lifecycle() return 204, {}, "" + elif "publicAccessBlock" in querystring: + self.backend.delete_bucket_public_access_block(bucket_name) + return 204, {}, "" removed_bucket = self.backend.delete_bucket(bucket_name) @@ -1460,7 +1491,9 @@ S3_ALL_BUCKETS = """ {{ bucket.name }} + {% if prefix != None %} {{ prefix }} + {% endif %} {{ max_keys }} {{ delimiter }} {{ is_truncated }} @@ -1492,7 +1525,9 @@ S3_BUCKET_GET_RESPONSE = """ S3_BUCKET_GET_RESPONSE_V2 = """ {{ bucket.name }} +{% if prefix != None %} {{ prefix }} +{% endif %} {{ max_keys }} {{ key_count }} {% if delimiter %} @@ -1653,7 +1688,9 @@ S3_BUCKET_GET_VERSIONING = """ S3_BUCKET_GET_VERSIONS = """ {{ bucket.name }} + {% if prefix != None %} {{ prefix }} + {% endif %} {{ key_marker }} {{ max_keys }} {{ is_truncated }} @@ -1856,7 +1893,8 @@ S3_MULTIPART_COMPLETE_RESPONSE = """ """ -S3_ALL_MULTIPARTS = """ +S3_ALL_MULTIPARTS = ( + """ {{ bucket_name }} @@ -1868,7 +1906,9 @@ S3_ALL_MULTIPARTS = """ {{ upload.key_name }} {{ upload.id }} - arn:aws:iam::123456789012:user/user1-11111a31-17b5-4fb7-9df5-b111111f13de + arn:aws:iam::""" + + ACCOUNT_ID + + """:user/user1-11111a31-17b5-4fb7-9df5-b111111f13de user1-11111a31-17b5-4fb7-9df5-b111111f13de @@ -1881,6 +1921,7 @@ S3_ALL_MULTIPARTS = """ {% endfor %} """ +) S3_NO_POLICY = """ @@ -2053,3 +2094,12 @@ S3_BUCKET_ACCELERATE = """ S3_BUCKET_ACCELERATE_NOT_SET = """ """ + +S3_PUBLIC_ACCESS_BLOCK_CONFIGURATION = """ + + {{public_block_config.block_public_acls}} + {{public_block_config.ignore_public_acls}} + {{public_block_config.block_public_policy}} + {{public_block_config.restrict_public_buckets}} + +""" diff --git a/moto/secretsmanager/models.py b/moto/secretsmanager/models.py index 2a1a336d9..294a6401e 100644 --- a/moto/secretsmanager/models.py +++ b/moto/secretsmanager/models.py @@ -6,7 +6,7 @@ import json import uuid import datetime -import boto3 +from boto3 import Session from moto.core import BaseBackend, BaseModel from .exceptions import ( @@ -491,7 +491,14 @@ class SecretsManagerBackend(BaseBackend): ) -available_regions = boto3.session.Session().get_available_regions("secretsmanager") -secretsmanager_backends = { - region: SecretsManagerBackend(region_name=region) for region in available_regions -} +secretsmanager_backends = {} +for region in Session().get_available_regions("secretsmanager"): + secretsmanager_backends[region] = SecretsManagerBackend(region_name=region) +for region in Session().get_available_regions( + "secretsmanager", partition_name="aws-us-gov" +): + secretsmanager_backends[region] = SecretsManagerBackend(region_name=region) +for region in Session().get_available_regions( + "secretsmanager", partition_name="aws-cn" +): + secretsmanager_backends[region] = SecretsManagerBackend(region_name=region) diff --git a/moto/ses/feedback.py b/moto/ses/feedback.py index c3d630e59..b0fa293e7 100644 --- a/moto/ses/feedback.py +++ b/moto/ses/feedback.py @@ -1,3 +1,5 @@ +from moto.core import ACCOUNT_ID + """ SES Feedback messages Extracted from https://docs.aws.amazon.com/ses/latest/DeveloperGuide/notification-contents.html @@ -10,7 +12,7 @@ COMMON_MAIL = { "source": "sender@example.com", "sourceArn": "arn:aws:ses:us-west-2:888888888888:identity/example.com", "sourceIp": "127.0.3.0", - "sendingAccountId": "123456789012", + "sendingAccountId": ACCOUNT_ID, "destination": ["recipient@example.com"], "headersTruncated": False, "headers": [ diff --git a/moto/sns/models.py b/moto/sns/models.py index 8b125358d..d6791eecf 100644 --- a/moto/sns/models.py +++ b/moto/sns/models.py @@ -31,7 +31,8 @@ from .exceptions import ( ) from .utils import make_arn_for_topic, make_arn_for_subscription, is_e164 -DEFAULT_ACCOUNT_ID = 123456789012 +from moto.core import ACCOUNT_ID as DEFAULT_ACCOUNT_ID + DEFAULT_PAGE_SIZE = 100 MAXIMUM_MESSAGE_LENGTH = 262144 # 256 KiB @@ -259,7 +260,9 @@ class Subscription(BaseModel): "SignatureVersion": "1", "Signature": "EXAMPLElDMXvB8r9R83tGoNn0ecwd5UjllzsvSvbItzfaMpN2nk5HVSw7XnOn/49IkxDKz8YrlH2qJXj2iZB0Zo2O71c4qQk1fMUDi3LGpij7RCW7AW9vYYsSqIKRnFS94ilu7NFhUzLiieYr4BKHpdTmdD6c0esKEYBpabxDSc=", "SigningCertURL": "https://sns.us-east-1.amazonaws.com/SimpleNotificationService-f3ecfb7224c7233fe7bb5f59f96de52f.pem", - "UnsubscribeURL": "https://sns.us-east-1.amazonaws.com/?Action=Unsubscribe&SubscriptionArn=arn:aws:sns:us-east-1:123456789012:some-topic:2bcfbf39-05c3-41de-beaa-fcfcc21c8f55", + "UnsubscribeURL": "https://sns.us-east-1.amazonaws.com/?Action=Unsubscribe&SubscriptionArn=arn:aws:sns:us-east-1:{}:some-topic:2bcfbf39-05c3-41de-beaa-fcfcc21c8f55".format( + DEFAULT_ACCOUNT_ID + ), } if message_attributes: post_data["MessageAttributes"] = message_attributes @@ -275,8 +278,11 @@ class PlatformApplication(BaseModel): @property def arn(self): - return "arn:aws:sns:{region}:123456789012:app/{platform}/{name}".format( - region=self.region, platform=self.platform, name=self.name + return "arn:aws:sns:{region}:{AccountId}:app/{platform}/{name}".format( + region=self.region, + platform=self.platform, + name=self.name, + AccountId=DEFAULT_ACCOUNT_ID, ) @@ -305,8 +311,9 @@ class PlatformEndpoint(BaseModel): @property def arn(self): - return "arn:aws:sns:{region}:123456789012:endpoint/{platform}/{name}/{id}".format( + return "arn:aws:sns:{region}:{AccountId}:endpoint/{platform}/{name}/{id}".format( region=self.region, + AccountId=DEFAULT_ACCOUNT_ID, platform=self.application.platform, name=self.application.name, id=self.id, @@ -390,10 +397,6 @@ class SNSBackend(BaseBackend): return self._get_values_nexttoken(self.topics, next_token) def delete_topic(self, arn): - topic = self.get_topic(arn) - subscriptions = self._get_topic_subscriptions(topic) - for sub in subscriptions: - self.unsubscribe(sub.arn) self.topics.pop(arn) def get_topic(self, arn): @@ -432,11 +435,18 @@ class SNSBackend(BaseBackend): subscription = Subscription(topic, endpoint, protocol) attributes = { "PendingConfirmation": "false", + "ConfirmationWasAuthenticated": "true", "Endpoint": endpoint, "TopicArn": topic_arn, "Protocol": protocol, "SubscriptionArn": subscription.arn, + "Owner": DEFAULT_ACCOUNT_ID, + "RawMessageDelivery": "false", } + + if protocol in ["http", "https"]: + attributes["EffectiveDeliveryPolicy"] = topic.effective_delivery_policy + subscription.attributes = attributes self.subscriptions[subscription.arn] = subscription return subscription @@ -452,7 +462,7 @@ class SNSBackend(BaseBackend): return None def unsubscribe(self, subscription_arn): - self.subscriptions.pop(subscription_arn) + self.subscriptions.pop(subscription_arn, None) def list_subscriptions(self, topic_arn=None, next_token=None): if topic_arn: @@ -693,21 +703,25 @@ class SNSBackend(BaseBackend): sns_backends = {} for region in Session().get_available_regions("sns"): sns_backends[region] = SNSBackend(region) +for region in Session().get_available_regions("sns", partition_name="aws-us-gov"): + sns_backends[region] = SNSBackend(region) +for region in Session().get_available_regions("sns", partition_name="aws-cn"): + sns_backends[region] = SNSBackend(region) DEFAULT_EFFECTIVE_DELIVERY_POLICY = { - "http": { - "disableSubscriptionOverrides": False, - "defaultHealthyRetryPolicy": { - "numNoDelayRetries": 0, - "numMinDelayRetries": 0, - "minDelayTarget": 20, - "maxDelayTarget": 20, - "numMaxDelayRetries": 0, - "numRetries": 3, - "backoffFunction": "linear", - }, - } + "defaultHealthyRetryPolicy": { + "numNoDelayRetries": 0, + "numMinDelayRetries": 0, + "minDelayTarget": 20, + "maxDelayTarget": 20, + "numMaxDelayRetries": 0, + "numRetries": 3, + "backoffFunction": "linear", + }, + "sicklyRetryPolicy": None, + "throttlePolicy": None, + "guaranteed": False, } diff --git a/moto/sqs/models.py b/moto/sqs/models.py index ca3d41f38..40dd6ba97 100644 --- a/moto/sqs/models.py +++ b/moto/sqs/models.py @@ -8,7 +8,7 @@ import six import struct from xml.sax.saxutils import escape -import boto.sqs +from boto3 import Session from moto.core.exceptions import RESTError from moto.core import BaseBackend, BaseModel @@ -32,7 +32,8 @@ from .exceptions import ( InvalidAttributeName, ) -DEFAULT_ACCOUNT_ID = 123456789012 +from moto.core import ACCOUNT_ID as DEFAULT_ACCOUNT_ID + DEFAULT_SENDER_ID = "AIDAIT2UOQQY3AUEKVGXU" MAXIMUM_MESSAGE_LENGTH = 262144 # 256 KiB @@ -417,8 +418,8 @@ class Queue(BaseModel): return result def url(self, request_url): - return "{0}://{1}/123456789012/{2}".format( - request_url.scheme, request_url.netloc, self.name + return "{0}://{1}/{2}/{3}".format( + request_url.scheme, request_url.netloc, DEFAULT_ACCOUNT_ID, self.name ) @property @@ -856,5 +857,9 @@ class SQSBackend(BaseBackend): sqs_backends = {} -for region in boto.sqs.regions(): - sqs_backends[region.name] = SQSBackend(region.name) +for region in Session().get_available_regions("sqs"): + sqs_backends[region] = SQSBackend(region) +for region in Session().get_available_regions("sqs", partition_name="aws-us-gov"): + sqs_backends[region] = SQSBackend(region) +for region in Session().get_available_regions("sqs", partition_name="aws-cn"): + sqs_backends[region] = SQSBackend(region) diff --git a/moto/ssm/models.py b/moto/ssm/models.py index 65b4d6743..60c47f021 100644 --- a/moto/ssm/models.py +++ b/moto/ssm/models.py @@ -13,6 +13,7 @@ import time import uuid import itertools +from .utils import parameter_arn from .exceptions import ( ValidationException, InvalidFilterValue, @@ -60,19 +61,23 @@ class Parameter(BaseModel): if value.startswith(prefix): return value[len(prefix) :] - def response_object(self, decrypt=False): + def response_object(self, decrypt=False, region=None): r = { "Name": self.name, "Type": self.type, "Value": self.decrypt(self.value) if decrypt else self.value, "Version": self.version, + "LastModifiedDate": round(self.last_modified_date, 3), } + if region: + r["ARN"] = parameter_arn(region, self.name) + return r def describe_response_object(self, decrypt=False): r = self.response_object(decrypt) - r["LastModifiedDate"] = int(self.last_modified_date) + r["LastModifiedDate"] = round(self.last_modified_date, 3) r["LastModifiedUser"] = "N/A" if self.description: @@ -510,7 +515,15 @@ class SimpleSystemManagerBackend(BaseBackend): result.append(self.get_parameter(name, with_decryption)) return result - def get_parameters_by_path(self, path, with_decryption, recursive, filters=None): + def get_parameters_by_path( + self, + path, + with_decryption, + recursive, + filters=None, + next_token=None, + max_results=10, + ): """Implement the get-parameters-by-path-API in the backend.""" result = [] # path could be with or without a trailing /. we handle this @@ -527,7 +540,19 @@ class SimpleSystemManagerBackend(BaseBackend): continue result.append(self.get_parameter(param_name, with_decryption)) - return result + return self._get_values_nexttoken(result, max_results, next_token) + + def _get_values_nexttoken(self, values_list, max_results, next_token=None): + if next_token is None: + next_token = 0 + next_token = int(next_token) + max_results = int(max_results) + values = values_list[next_token : next_token + max_results] + if len(values) == max_results: + next_token = str(next_token + max_results) + else: + next_token = None + return values, next_token def get_parameter_history(self, name, with_decryption): if name in self._parameters: diff --git a/moto/ssm/responses.py b/moto/ssm/responses.py index 29fd8820c..1b13780a8 100644 --- a/moto/ssm/responses.py +++ b/moto/ssm/responses.py @@ -51,7 +51,7 @@ class SimpleSystemManagerResponse(BaseResponse): } return json.dumps(error), dict(status=400) - response = {"Parameter": result.response_object(with_decryption)} + response = {"Parameter": result.response_object(with_decryption, self.region)} return json.dumps(response) def get_parameters(self): @@ -63,7 +63,7 @@ class SimpleSystemManagerResponse(BaseResponse): response = {"Parameters": [], "InvalidParameters": []} for parameter in result: - param_data = parameter.response_object(with_decryption) + param_data = parameter.response_object(with_decryption, self.region) response["Parameters"].append(param_data) param_names = [param.name for param in result] @@ -77,15 +77,22 @@ class SimpleSystemManagerResponse(BaseResponse): with_decryption = self._get_param("WithDecryption") recursive = self._get_param("Recursive", False) filters = self._get_param("ParameterFilters") + token = self._get_param("NextToken") + max_results = self._get_param("MaxResults", 10) - result = self.ssm_backend.get_parameters_by_path( - path, with_decryption, recursive, filters + result, next_token = self.ssm_backend.get_parameters_by_path( + path, + with_decryption, + recursive, + filters, + next_token=token, + max_results=max_results, ) - response = {"Parameters": []} + response = {"Parameters": [], "NextToken": next_token} for parameter in result: - param_data = parameter.response_object(with_decryption) + param_data = parameter.response_object(with_decryption, self.region) response["Parameters"].append(param_data) return json.dumps(response) diff --git a/moto/ssm/utils.py b/moto/ssm/utils.py new file mode 100644 index 000000000..3f3762d1c --- /dev/null +++ b/moto/ssm/utils.py @@ -0,0 +1,9 @@ +ACCOUNT_ID = "1234567890" + + +def parameter_arn(region, parameter_name): + if parameter_name[0] == "/": + parameter_name = parameter_name[1:] + return "arn:aws:ssm:{0}:{1}:parameter/{2}".format( + region, ACCOUNT_ID, parameter_name + ) diff --git a/moto/stepfunctions/models.py b/moto/stepfunctions/models.py index 665f3b777..de530b863 100644 --- a/moto/stepfunctions/models.py +++ b/moto/stepfunctions/models.py @@ -1,6 +1,8 @@ -import boto import re from datetime import datetime + +from boto3 import Session + from moto.core import BaseBackend from moto.core.utils import iso_8601_datetime_without_milliseconds from moto.sts.models import ACCOUNT_ID @@ -280,7 +282,12 @@ class StepFunctionBackend(BaseBackend): return ACCOUNT_ID -stepfunction_backends = { - _region.name: StepFunctionBackend(_region.name) - for _region in boto.awslambda.regions() -} +stepfunction_backends = {} +for region in Session().get_available_regions("stepfunctions"): + stepfunction_backends[region] = StepFunctionBackend(region) +for region in Session().get_available_regions( + "stepfunctions", partition_name="aws-us-gov" +): + stepfunction_backends[region] = StepFunctionBackend(region) +for region in Session().get_available_regions("stepfunctions", partition_name="aws-cn"): + stepfunction_backends[region] = StepFunctionBackend(region) diff --git a/moto/sts/models.py b/moto/sts/models.py index d3afc9904..12824b2ed 100644 --- a/moto/sts/models.py +++ b/moto/sts/models.py @@ -2,7 +2,7 @@ from __future__ import unicode_literals import datetime from moto.core import BaseBackend, BaseModel from moto.core.utils import iso_8601_datetime_with_milliseconds -from moto.iam.models import ACCOUNT_ID +from moto.core import ACCOUNT_ID from moto.sts.utils import ( random_access_key_id, random_secret_access_key, diff --git a/moto/sts/responses.py b/moto/sts/responses.py index f6d8647c4..f36799b03 100644 --- a/moto/sts/responses.py +++ b/moto/sts/responses.py @@ -1,7 +1,7 @@ from __future__ import unicode_literals from moto.core.responses import BaseResponse -from moto.iam.models import ACCOUNT_ID +from moto.core import ACCOUNT_ID from moto.iam import iam_backend from .exceptions import STSValidationError from .models import sts_backend diff --git a/moto/swf/models/__init__.py b/moto/swf/models/__init__.py index 50cc29bb3..e5b285f5b 100644 --- a/moto/swf/models/__init__.py +++ b/moto/swf/models/__init__.py @@ -1,6 +1,6 @@ from __future__ import unicode_literals -import boto.swf +from boto3 import Session from moto.core import BaseBackend @@ -418,5 +418,9 @@ class SWFBackend(BaseBackend): swf_backends = {} -for region in boto.swf.regions(): - swf_backends[region.name] = SWFBackend(region.name) +for region in Session().get_available_regions("swf"): + swf_backends[region] = SWFBackend(region) +for region in Session().get_available_regions("swf", partition_name="aws-us-gov"): + swf_backends[region] = SWFBackend(region) +for region in Session().get_available_regions("swf", partition_name="aws-cn"): + swf_backends[region] = SWFBackend(region) diff --git a/requirements-dev.txt b/requirements-dev.txt index 436a7a51b..c5f055a26 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -2,8 +2,9 @@ mock nose black; python_version >= '3.6' +regex==2019.11.1; python_version >= '3.6' # Needed for black sure==1.4.11 -coverage +coverage==4.5.4 flake8==3.7.8 freezegun flask diff --git a/scripts/template/lib/models.py.j2 b/scripts/template/lib/models.py.j2 index 28fa4a4e1..84f8dad71 100644 --- a/scripts/template/lib/models.py.j2 +++ b/scripts/template/lib/models.py.j2 @@ -1,5 +1,5 @@ from __future__ import unicode_literals -import boto3 +from boto3 import Session from moto.core import BaseBackend, BaseModel @@ -16,5 +16,10 @@ class {{ service_class }}Backend(BaseBackend): # add methods from here -available_regions = boto3.session.Session().get_available_regions("{{ service }}") -{{ escaped_service }}_backends = {region: {{ service_class }}Backend(region) for region in available_regions} +{{ escaped_service }}_backends = {} +for region in Session().get_available_regions("{{ service }}"): + {{ escaped_service }}_backends[region] = {{ service_class }}Backend() +for region in Session().get_available_regions("{{ service }}", partition_name="aws-us-gov"): + {{ escaped_service }}_backends[region] = {{ service_class }}Backend() +for region in Session().get_available_regions("{{ service }}", partition_name="aws-cn"): + {{ escaped_service }}_backends[region] = {{ service_class }}Backend() diff --git a/tests/test_acm/test_acm.py b/tests/test_acm/test_acm.py index 6f879e55e..b38cd1843 100644 --- a/tests/test_acm/test_acm.py +++ b/tests/test_acm/test_acm.py @@ -9,6 +9,7 @@ import uuid from botocore.exceptions import ClientError from moto import mock_acm +from moto.core import ACCOUNT_ID RESOURCE_FOLDER = os.path.join(os.path.dirname(__file__), "resources") @@ -19,7 +20,9 @@ SERVER_CRT = _GET_RESOURCE("star_moto_com.pem") SERVER_COMMON_NAME = "*.moto.com" SERVER_CRT_BAD = _GET_RESOURCE("star_moto_com-bad.pem") SERVER_KEY = _GET_RESOURCE("star_moto_com.key") -BAD_ARN = "arn:aws:acm:us-east-2:123456789012:certificate/_0000000-0000-0000-0000-000000000000" +BAD_ARN = "arn:aws:acm:us-east-2:{}:certificate/_0000000-0000-0000-0000-000000000000".format( + ACCOUNT_ID +) def _import_cert(client): diff --git a/tests/test_apigateway/test_apigateway.py b/tests/test_apigateway/test_apigateway.py index 8da5efa39..59c0c07f6 100644 --- a/tests/test_apigateway/test_apigateway.py +++ b/tests/test_apigateway/test_apigateway.py @@ -9,6 +9,7 @@ from botocore.exceptions import ClientError import responses from moto import mock_apigateway, settings +from moto.core import ACCOUNT_ID from nose.tools import assert_raises @@ -881,7 +882,9 @@ def test_put_integration_validation(): client.put_integration( restApiId=api_id, resourceId=root_id, - credentials="arn:aws:iam::123456789012:role/service-role/testfunction-role-oe783psq", + credentials="arn:aws:iam::{}:role/service-role/testfunction-role-oe783psq".format( + ACCOUNT_ID + ), httpMethod="GET", type=type, uri="arn:aws:apigateway:us-west-2:s3:path/b/k", @@ -903,7 +906,9 @@ def test_put_integration_validation(): client.put_integration( restApiId=api_id, resourceId=root_id, - credentials="arn:aws:iam::123456789012:role/service-role/testfunction-role-oe783psq", + credentials="arn:aws:iam::{}:role/service-role/testfunction-role-oe783psq".format( + ACCOUNT_ID + ), httpMethod="GET", type=type, uri="arn:aws:apigateway:us-west-2:s3:path/b/k", @@ -1130,6 +1135,23 @@ def test_http_proxying_integration(): requests.get(deploy_url).content.should.equal(b"a fake response") +@mock_apigateway +def test_create_api_key(): + region_name = "us-west-2" + client = boto3.client("apigateway", region_name=region_name) + + apikey_value = "12345" + apikey_name = "TESTKEY1" + payload = {"value": apikey_value, "name": apikey_name} + + client.create_api_key(**payload) + + response = client.get_api_keys() + len(response["items"]).should.equal(1) + + client.create_api_key.when.called_with(**payload).should.throw(ClientError) + + @mock_apigateway def test_api_keys(): region_name = "us-west-2" @@ -1139,26 +1161,18 @@ def test_api_keys(): apikey_value = "12345" apikey_name = "TESTKEY1" - payload = {"value": apikey_value, "name": apikey_name} + payload = { + "value": apikey_value, + "name": apikey_name, + "tags": {"tag1": "test_tag1", "tag2": "1"}, + } response = client.create_api_key(**payload) + apikey_id = response["id"] apikey = client.get_api_key(apiKey=response["id"]) apikey["name"].should.equal(apikey_name) apikey["value"].should.equal(apikey_value) - - apikey_name = "TESTKEY2" - payload = {"name": apikey_name, "tags": {"tag1": "test_tag1", "tag2": "1"}} - response = client.create_api_key(**payload) - apikey_id = response["id"] - apikey = client.get_api_key(apiKey=apikey_id) - apikey["name"].should.equal(apikey_name) apikey["tags"]["tag1"].should.equal("test_tag1") apikey["tags"]["tag2"].should.equal("1") - len(apikey["value"]).should.equal(40) - - apikey_name = "TESTKEY3" - payload = {"name": apikey_name} - response = client.create_api_key(**payload) - apikey_id = response["id"] patch_operations = [ {"op": "replace", "path": "/name", "value": "TESTKEY3_CHANGE"}, @@ -1172,13 +1186,25 @@ def test_api_keys(): response["description"].should.equal("APIKEY UPDATE TEST") response["enabled"].should.equal(False) + updated_api_key = client.get_api_key(apiKey=apikey_id) + updated_api_key["name"].should.equal("TESTKEY3_CHANGE") + updated_api_key["customerId"].should.equal("12345") + updated_api_key["description"].should.equal("APIKEY UPDATE TEST") + updated_api_key["enabled"].should.equal(False) + response = client.get_api_keys() - len(response["items"]).should.equal(3) + len(response["items"]).should.equal(1) + + payload = {"name": apikey_name} + client.create_api_key(**payload) + + response = client.get_api_keys() + len(response["items"]).should.equal(2) client.delete_api_key(apiKey=apikey_id) response = client.get_api_keys() - len(response["items"]).should.equal(2) + len(response["items"]).should.equal(1) @mock_apigateway diff --git a/tests/test_autoscaling/test_launch_configurations.py b/tests/test_autoscaling/test_launch_configurations.py index 8cd596ee7..ab2743f54 100644 --- a/tests/test_autoscaling/test_launch_configurations.py +++ b/tests/test_autoscaling/test_launch_configurations.py @@ -8,6 +8,7 @@ import sure # noqa from moto import mock_autoscaling_deprecated from moto import mock_autoscaling +from moto.core import ACCOUNT_ID from tests.helpers import requires_boto_gte @@ -22,7 +23,9 @@ def test_create_launch_configuration(): security_groups=["default", "default2"], user_data=b"This is some user_data", instance_monitoring=True, - instance_profile_name="arn:aws:iam::123456789012:instance-profile/testing", + instance_profile_name="arn:aws:iam::{}:instance-profile/testing".format( + ACCOUNT_ID + ), spot_price=0.1, ) conn.create_launch_configuration(config) @@ -36,7 +39,7 @@ def test_create_launch_configuration(): launch_config.user_data.should.equal(b"This is some user_data") launch_config.instance_monitoring.enabled.should.equal("true") launch_config.instance_profile_name.should.equal( - "arn:aws:iam::123456789012:instance-profile/testing" + "arn:aws:iam::{}:instance-profile/testing".format(ACCOUNT_ID) ) launch_config.spot_price.should.equal(0.1) @@ -71,7 +74,9 @@ def test_create_launch_configuration_with_block_device_mappings(): security_groups=["default", "default2"], user_data=b"This is some user_data", instance_monitoring=True, - instance_profile_name="arn:aws:iam::123456789012:instance-profile/testing", + instance_profile_name="arn:aws:iam::{}:instance-profile/testing".format( + ACCOUNT_ID + ), spot_price=0.1, block_device_mappings=[block_device_mapping], ) @@ -86,7 +91,7 @@ def test_create_launch_configuration_with_block_device_mappings(): launch_config.user_data.should.equal(b"This is some user_data") launch_config.instance_monitoring.enabled.should.equal("true") launch_config.instance_profile_name.should.equal( - "arn:aws:iam::123456789012:instance-profile/testing" + "arn:aws:iam::{}:instance-profile/testing".format(ACCOUNT_ID) ) launch_config.spot_price.should.equal(0.1) len(launch_config.block_device_mappings).should.equal(3) diff --git a/tests/test_awslambda/test_lambda.py b/tests/test_awslambda/test_lambda.py index e0e8a8205..6fd97e325 100644 --- a/tests/test_awslambda/test_lambda.py +++ b/tests/test_awslambda/test_lambda.py @@ -306,8 +306,8 @@ def test_create_function_from_aws_bucket(): result.should.equal( { "FunctionName": "testFunction", - "FunctionArn": "arn:aws:lambda:{}:123456789012:function:testFunction".format( - _lambda_region + "FunctionArn": "arn:aws:lambda:{}:{}:function:testFunction".format( + _lambda_region, ACCOUNT_ID ), "Runtime": "python2.7", "Role": result["Role"], @@ -353,8 +353,8 @@ def test_create_function_from_zipfile(): result.should.equal( { "FunctionName": "testFunction", - "FunctionArn": "arn:aws:lambda:{}:123456789012:function:testFunction".format( - _lambda_region + "FunctionArn": "arn:aws:lambda:{}:{}:function:testFunction".format( + _lambda_region, ACCOUNT_ID ), "Runtime": "python2.7", "Role": result["Role"], @@ -431,7 +431,7 @@ def test_get_function(): result = conn.get_function(FunctionName="testFunction", Qualifier="$LATEST") result["Configuration"]["Version"].should.equal("$LATEST") result["Configuration"]["FunctionArn"].should.equal( - "arn:aws:lambda:us-west-2:123456789012:function:testFunction:$LATEST" + "arn:aws:lambda:us-west-2:{}:function:testFunction:$LATEST".format(ACCOUNT_ID) ) # Test get function when can't find function name @@ -620,8 +620,8 @@ def test_list_create_list_get_delete_list(): "CodeSha256": hashlib.sha256(zip_content).hexdigest(), "CodeSize": len(zip_content), "Description": "test lambda function", - "FunctionArn": "arn:aws:lambda:{}:123456789012:function:testFunction".format( - _lambda_region + "FunctionArn": "arn:aws:lambda:{}:{}:function:testFunction".format( + _lambda_region, ACCOUNT_ID ), "FunctionName": "testFunction", "Handler": "lambda_function.lambda_handler", @@ -749,16 +749,17 @@ def test_tags_not_found(): """ conn = boto3.client("lambda", "us-west-2") conn.list_tags.when.called_with( - Resource="arn:aws:lambda:123456789012:function:not-found" + Resource="arn:aws:lambda:{}:function:not-found".format(ACCOUNT_ID) ).should.throw(botocore.client.ClientError) conn.tag_resource.when.called_with( - Resource="arn:aws:lambda:123456789012:function:not-found", + Resource="arn:aws:lambda:{}:function:not-found".format(ACCOUNT_ID), Tags=dict(spam="eggs"), ).should.throw(botocore.client.ClientError) conn.untag_resource.when.called_with( - Resource="arn:aws:lambda:123456789012:function:not-found", TagKeys=["spam"] + Resource="arn:aws:lambda:{}:function:not-found".format(ACCOUNT_ID), + TagKeys=["spam"], ).should.throw(botocore.client.ClientError) @@ -815,8 +816,8 @@ def test_get_function_created_with_zipfile(): "CodeSha256": hashlib.sha256(zip_content).hexdigest(), "CodeSize": len(zip_content), "Description": "test lambda function", - "FunctionArn": "arn:aws:lambda:{}:123456789012:function:testFunction".format( - _lambda_region + "FunctionArn": "arn:aws:lambda:{}:{}:function:testFunction".format( + _lambda_region, ACCOUNT_ID ), "FunctionName": "testFunction", "Handler": "lambda_function.handler", @@ -921,18 +922,15 @@ def test_list_versions_by_function(): assert res["ResponseMetadata"]["HTTPStatusCode"] == 201 versions = conn.list_versions_by_function(FunctionName="testFunction") assert len(versions["Versions"]) == 3 - assert ( - versions["Versions"][0]["FunctionArn"] - == "arn:aws:lambda:us-west-2:123456789012:function:testFunction:$LATEST" - ) - assert ( - versions["Versions"][1]["FunctionArn"] - == "arn:aws:lambda:us-west-2:123456789012:function:testFunction:1" - ) - assert ( - versions["Versions"][2]["FunctionArn"] - == "arn:aws:lambda:us-west-2:123456789012:function:testFunction:2" - ) + assert versions["Versions"][0][ + "FunctionArn" + ] == "arn:aws:lambda:us-west-2:{}:function:testFunction:$LATEST".format(ACCOUNT_ID) + assert versions["Versions"][1][ + "FunctionArn" + ] == "arn:aws:lambda:us-west-2:{}:function:testFunction:1".format(ACCOUNT_ID) + assert versions["Versions"][2][ + "FunctionArn" + ] == "arn:aws:lambda:us-west-2:{}:function:testFunction:2".format(ACCOUNT_ID) conn.create_function( FunctionName="testFunction_2", @@ -947,9 +945,10 @@ def test_list_versions_by_function(): ) versions = conn.list_versions_by_function(FunctionName="testFunction_2") assert len(versions["Versions"]) == 1 - assert ( - versions["Versions"][0]["FunctionArn"] - == "arn:aws:lambda:us-west-2:123456789012:function:testFunction_2:$LATEST" + assert versions["Versions"][0][ + "FunctionArn" + ] == "arn:aws:lambda:us-west-2:{}:function:testFunction_2:$LATEST".format( + ACCOUNT_ID ) @@ -1426,8 +1425,8 @@ def test_update_function_zip(): "CodeSha256": hashlib.sha256(zip_content_two).hexdigest(), "CodeSize": len(zip_content_two), "Description": "test lambda function", - "FunctionArn": "arn:aws:lambda:{}:123456789012:function:testFunctionZip:2".format( - _lambda_region + "FunctionArn": "arn:aws:lambda:{}:{}:function:testFunctionZip:2".format( + _lambda_region, ACCOUNT_ID ), "FunctionName": "testFunctionZip", "Handler": "lambda_function.lambda_handler", @@ -1488,8 +1487,8 @@ def test_update_function_s3(): "CodeSha256": hashlib.sha256(zip_content_two).hexdigest(), "CodeSize": len(zip_content_two), "Description": "test lambda function", - "FunctionArn": "arn:aws:lambda:{}:123456789012:function:testFunctionS3:2".format( - _lambda_region + "FunctionArn": "arn:aws:lambda:{}:{}:function:testFunctionS3:2".format( + _lambda_region, ACCOUNT_ID ), "FunctionName": "testFunctionS3", "Handler": "lambda_function.lambda_handler", diff --git a/tests/test_batch/test_batch.py b/tests/test_batch/test_batch.py index 691d90b6d..141d6b343 100644 --- a/tests/test_batch/test_batch.py +++ b/tests/test_batch/test_batch.py @@ -55,6 +55,10 @@ def _setup(ec2_client, iam_client): RoleName="TestRole", AssumeRolePolicyDocument="some_policy" ) iam_arn = resp["Role"]["Arn"] + iam_client.create_instance_profile(InstanceProfileName="TestRole") + iam_client.add_role_to_instance_profile( + InstanceProfileName="TestRole", RoleName="TestRole" + ) return vpc_id, subnet_id, sg_id, iam_arn @@ -83,7 +87,7 @@ def test_create_managed_compute_environment(): "subnets": [subnet_id], "securityGroupIds": [sg_id], "ec2KeyPair": "string", - "instanceRole": iam_arn, + "instanceRole": iam_arn.replace("role", "instance-profile"), "tags": {"string": "string"}, "bidPercentage": 123, "spotIamFleetRole": "string", @@ -209,7 +213,7 @@ def test_delete_managed_compute_environment(): "subnets": [subnet_id], "securityGroupIds": [sg_id], "ec2KeyPair": "string", - "instanceRole": iam_arn, + "instanceRole": iam_arn.replace("role", "instance-profile"), "tags": {"string": "string"}, "bidPercentage": 123, "spotIamFleetRole": "string", @@ -608,6 +612,9 @@ def test_describe_task_definition(): resp = batch_client.describe_job_definitions(jobDefinitions=["sleep10", "test1"]) len(resp["jobDefinitions"]).should.equal(3) + for job_definition in resp["jobDefinitions"]: + job_definition["status"].should.equal("ACTIVE") + @mock_logs @mock_ec2 diff --git a/tests/test_batch/test_cloudformation.py b/tests/test_batch/test_cloudformation.py index a8b94f3a3..a6baedb38 100644 --- a/tests/test_batch/test_cloudformation.py +++ b/tests/test_batch/test_cloudformation.py @@ -51,6 +51,10 @@ def _setup(ec2_client, iam_client): RoleName="TestRole", AssumeRolePolicyDocument="some_policy" ) iam_arn = resp["Role"]["Arn"] + iam_client.create_instance_profile(InstanceProfileName="TestRole") + iam_client.add_role_to_instance_profile( + InstanceProfileName="TestRole", RoleName="TestRole" + ) return vpc_id, subnet_id, sg_id, iam_arn @@ -78,7 +82,7 @@ def test_create_env_cf(): "InstanceTypes": ["optimal"], "Subnets": [subnet_id], "SecurityGroupIds": [sg_id], - "InstanceRole": iam_arn, + "InstanceRole": iam_arn.replace("role", "instance-profile"), }, "ServiceRole": iam_arn, }, @@ -129,7 +133,7 @@ def test_create_job_queue_cf(): "InstanceTypes": ["optimal"], "Subnets": [subnet_id], "SecurityGroupIds": [sg_id], - "InstanceRole": iam_arn, + "InstanceRole": iam_arn.replace("role", "instance-profile"), }, "ServiceRole": iam_arn, }, @@ -195,7 +199,7 @@ def test_create_job_def_cf(): "InstanceTypes": ["optimal"], "Subnets": [subnet_id], "SecurityGroupIds": [sg_id], - "InstanceRole": iam_arn, + "InstanceRole": iam_arn.replace("role", "instance-profile"), }, "ServiceRole": iam_arn, }, diff --git a/tests/test_cloudformation/test_cloudformation_stack_crud.py b/tests/test_cloudformation/test_cloudformation_stack_crud.py index 3de758a02..75f705ea7 100644 --- a/tests/test_cloudformation/test_cloudformation_stack_crud.py +++ b/tests/test_cloudformation/test_cloudformation_stack_crud.py @@ -14,6 +14,7 @@ import sure # noqa # Ensure 'assert_raises' context manager support for Python 2.6 import tests.backport_assert_raises # noqa from nose.tools import assert_raises +from moto.core import ACCOUNT_ID from moto import ( mock_cloudformation_deprecated, @@ -129,12 +130,12 @@ def test_create_stack_with_notification_arn(): conn.create_stack( "test_stack_with_notifications", template_body=dummy_template_json, - notification_arns="arn:aws:sns:us-east-1:123456789012:fake-queue", + notification_arns="arn:aws:sns:us-east-1:{}:fake-queue".format(ACCOUNT_ID), ) stack = conn.describe_stacks()[0] [n.value for n in stack.notification_arns].should.contain( - "arn:aws:sns:us-east-1:123456789012:fake-queue" + "arn:aws:sns:us-east-1:{}:fake-queue".format(ACCOUNT_ID) ) diff --git a/tests/test_cloudformation/test_cloudformation_stack_crud_boto3.py b/tests/test_cloudformation/test_cloudformation_stack_crud_boto3.py index 28d68fa20..40fb2d669 100644 --- a/tests/test_cloudformation/test_cloudformation_stack_crud_boto3.py +++ b/tests/test_cloudformation/test_cloudformation_stack_crud_boto3.py @@ -11,6 +11,7 @@ import sure # noqa from nose.tools import assert_raises from moto import mock_cloudformation, mock_s3, mock_sqs, mock_ec2 +from moto.core import ACCOUNT_ID dummy_template = { "AWSTemplateFormatVersion": "2010-09-09", @@ -174,17 +175,17 @@ def test_boto3_describe_stack_instances(): ) cf_conn.create_stack_instances( StackSetName="test_stack_set", - Accounts=["123456789012"], + Accounts=[ACCOUNT_ID], Regions=["us-east-1", "us-west-2"], ) usw2_instance = cf_conn.describe_stack_instance( StackSetName="test_stack_set", - StackInstanceAccount="123456789012", + StackInstanceAccount=ACCOUNT_ID, StackInstanceRegion="us-west-2", ) use1_instance = cf_conn.describe_stack_instance( StackSetName="test_stack_set", - StackInstanceAccount="123456789012", + StackInstanceAccount=ACCOUNT_ID, StackInstanceRegion="us-east-1", ) @@ -192,13 +193,13 @@ def test_boto3_describe_stack_instances(): "us-west-2" ) usw2_instance["StackInstance"].should.have.key("Account").which.should.equal( - "123456789012" + ACCOUNT_ID ) use1_instance["StackInstance"].should.have.key("Region").which.should.equal( "us-east-1" ) use1_instance["StackInstance"].should.have.key("Account").which.should.equal( - "123456789012" + ACCOUNT_ID ) @@ -236,7 +237,7 @@ def test_boto3_stop_stack_set_operation(): ) cf_conn.create_stack_instances( StackSetName="test_stack_set", - Accounts=["123456789012"], + Accounts=[ACCOUNT_ID], Regions=["us-east-1", "us-west-1", "us-west-2"], ) operation_id = cf_conn.list_stack_set_operations(StackSetName="test_stack_set")[ @@ -257,7 +258,7 @@ def test_boto3_describe_stack_set_operation(): ) cf_conn.create_stack_instances( StackSetName="test_stack_set", - Accounts=["123456789012"], + Accounts=[ACCOUNT_ID], Regions=["us-east-1", "us-west-1", "us-west-2"], ) operation_id = cf_conn.list_stack_set_operations(StackSetName="test_stack_set")[ @@ -282,7 +283,7 @@ def test_boto3_list_stack_set_operation_results(): ) cf_conn.create_stack_instances( StackSetName="test_stack_set", - Accounts=["123456789012"], + Accounts=[ACCOUNT_ID], Regions=["us-east-1", "us-west-1", "us-west-2"], ) operation_id = cf_conn.list_stack_set_operations(StackSetName="test_stack_set")[ @@ -297,9 +298,7 @@ def test_boto3_list_stack_set_operation_results(): ) response["Summaries"].should.have.length_of(3) - response["Summaries"][0].should.have.key("Account").which.should.equal( - "123456789012" - ) + response["Summaries"][0].should.have.key("Account").which.should.equal(ACCOUNT_ID) response["Summaries"][1].should.have.key("Status").which.should.equal("STOPPED") @@ -321,28 +320,28 @@ def test_boto3_update_stack_instances(): ) cf_conn.create_stack_instances( StackSetName="test_stack_set", - Accounts=["123456789012"], + Accounts=[ACCOUNT_ID], Regions=["us-east-1", "us-west-1", "us-west-2"], ) cf_conn.update_stack_instances( StackSetName="test_stack_set", - Accounts=["123456789012"], + Accounts=[ACCOUNT_ID], Regions=["us-west-1", "us-west-2"], ParameterOverrides=param_overrides, ) usw2_instance = cf_conn.describe_stack_instance( StackSetName="test_stack_set", - StackInstanceAccount="123456789012", + StackInstanceAccount=ACCOUNT_ID, StackInstanceRegion="us-west-2", ) usw1_instance = cf_conn.describe_stack_instance( StackSetName="test_stack_set", - StackInstanceAccount="123456789012", + StackInstanceAccount=ACCOUNT_ID, StackInstanceRegion="us-west-1", ) use1_instance = cf_conn.describe_stack_instance( StackSetName="test_stack_set", - StackInstanceAccount="123456789012", + StackInstanceAccount=ACCOUNT_ID, StackInstanceRegion="us-east-1", ) @@ -383,13 +382,13 @@ def test_boto3_delete_stack_instances(): ) cf_conn.create_stack_instances( StackSetName="test_stack_set", - Accounts=["123456789012"], + Accounts=[ACCOUNT_ID], Regions=["us-east-1", "us-west-2"], ) cf_conn.delete_stack_instances( StackSetName="test_stack_set", - Accounts=["123456789012"], + Accounts=[ACCOUNT_ID], Regions=["us-east-1"], RetainStacks=False, ) @@ -410,7 +409,7 @@ def test_boto3_create_stack_instances(): ) cf_conn.create_stack_instances( StackSetName="test_stack_set", - Accounts=["123456789012"], + Accounts=[ACCOUNT_ID], Regions=["us-east-1", "us-west-2"], ) @@ -419,7 +418,7 @@ def test_boto3_create_stack_instances(): ].should.have.length_of(2) cf_conn.list_stack_instances(StackSetName="test_stack_set")["Summaries"][0][ "Account" - ].should.equal("123456789012") + ].should.equal(ACCOUNT_ID) @mock_cloudformation @@ -440,13 +439,13 @@ def test_boto3_create_stack_instances_with_param_overrides(): ) cf_conn.create_stack_instances( StackSetName="test_stack_set", - Accounts=["123456789012"], + Accounts=[ACCOUNT_ID], Regions=["us-east-1", "us-west-2"], ParameterOverrides=param_overrides, ) usw2_instance = cf_conn.describe_stack_instance( StackSetName="test_stack_set", - StackInstanceAccount="123456789012", + StackInstanceAccount=ACCOUNT_ID, StackInstanceRegion="us-west-2", ) @@ -509,12 +508,12 @@ def test_boto3_list_stack_set_operations(): ) cf_conn.create_stack_instances( StackSetName="test_stack_set", - Accounts=["123456789012"], + Accounts=[ACCOUNT_ID], Regions=["us-east-1", "us-west-2"], ) cf_conn.update_stack_instances( StackSetName="test_stack_set", - Accounts=["123456789012"], + Accounts=[ACCOUNT_ID], Regions=["us-east-1", "us-west-2"], ) @@ -682,12 +681,12 @@ def test_create_stack_with_notification_arn(): cf.create_stack( StackName="test_stack_with_notifications", TemplateBody=dummy_template_json, - NotificationARNs=["arn:aws:sns:us-east-1:123456789012:fake-queue"], + NotificationARNs=["arn:aws:sns:us-east-1:{}:fake-queue".format(ACCOUNT_ID)], ) stack = list(cf.stacks.all())[0] stack.notification_arns.should.contain( - "arn:aws:sns:us-east-1:123456789012:fake-queue" + "arn:aws:sns:us-east-1:{}:fake-queue".format(ACCOUNT_ID) ) @@ -697,10 +696,10 @@ def test_create_stack_with_role_arn(): cf.create_stack( StackName="test_stack_with_notifications", TemplateBody=dummy_template_json, - RoleARN="arn:aws:iam::123456789012:role/moto", + RoleARN="arn:aws:iam::{}:role/moto".format(ACCOUNT_ID), ) stack = list(cf.stacks.all())[0] - stack.role_arn.should.equal("arn:aws:iam::123456789012:role/moto") + stack.role_arn.should.equal("arn:aws:iam::{}:role/moto".format(ACCOUNT_ID)) @mock_cloudformation @@ -1019,7 +1018,7 @@ def test_describe_updated_stack(): cf_conn.update_stack( StackName="test_stack", - RoleARN="arn:aws:iam::123456789012:role/moto", + RoleARN="arn:aws:iam::{}:role/moto".format(ACCOUNT_ID), TemplateBody=dummy_update_template_json, Tags=[{"Key": "foo", "Value": "baz"}], ) @@ -1030,7 +1029,7 @@ def test_describe_updated_stack(): stack_by_id["StackId"].should.equal(stack["StackId"]) stack_by_id["StackName"].should.equal("test_stack") stack_by_id["StackStatus"].should.equal("UPDATE_COMPLETE") - stack_by_id["RoleARN"].should.equal("arn:aws:iam::123456789012:role/moto") + stack_by_id["RoleARN"].should.equal("arn:aws:iam::{}:role/moto".format(ACCOUNT_ID)) stack_by_id["Tags"].should.equal([{"Key": "foo", "Value": "baz"}]) diff --git a/tests/test_cloudformation/test_cloudformation_stack_integration.py b/tests/test_cloudformation/test_cloudformation_stack_integration.py index e789f6e6b..e296ef2ed 100644 --- a/tests/test_cloudformation/test_cloudformation_stack_integration.py +++ b/tests/test_cloudformation/test_cloudformation_stack_integration.py @@ -43,6 +43,7 @@ from moto import ( mock_sqs_deprecated, mock_elbv2, ) +from moto.core import ACCOUNT_ID from moto.dynamodb2.models import Table from .fixtures import ( @@ -1912,7 +1913,7 @@ def test_stack_spot_fleet(): "Type": "AWS::EC2::SpotFleet", "Properties": { "SpotFleetRequestConfigData": { - "IamFleetRole": "arn:aws:iam::123456789012:role/fleet", + "IamFleetRole": "arn:aws:iam::{}:role/fleet".format(ACCOUNT_ID), "SpotPrice": "0.12", "TargetCapacity": 6, "AllocationStrategy": "diversified", @@ -1933,7 +1934,9 @@ def test_stack_spot_fleet(): "SecurityGroups": [{"GroupId": "sg-123"}], "SubnetId": subnet_id, "IamInstanceProfile": { - "Arn": "arn:aws:iam::123456789012:role/fleet" + "Arn": "arn:aws:iam::{}:role/fleet".format( + ACCOUNT_ID + ) }, "WeightedCapacity": "4", "SpotPrice": "10.00", @@ -1966,7 +1969,7 @@ def test_stack_spot_fleet(): spot_fleet_config["SpotPrice"].should.equal("0.12") spot_fleet_config["TargetCapacity"].should.equal(6) spot_fleet_config["IamFleetRole"].should.equal( - "arn:aws:iam::123456789012:role/fleet" + "arn:aws:iam::{}:role/fleet".format(ACCOUNT_ID) ) spot_fleet_config["AllocationStrategy"].should.equal("diversified") spot_fleet_config["FulfilledCapacity"].should.equal(6.0) @@ -1999,7 +2002,7 @@ def test_stack_spot_fleet_should_figure_out_default_price(): "Type": "AWS::EC2::SpotFleet", "Properties": { "SpotFleetRequestConfigData": { - "IamFleetRole": "arn:aws:iam::123456789012:role/fleet", + "IamFleetRole": "arn:aws:iam::{}:role/fleet".format(ACCOUNT_ID), "TargetCapacity": 6, "AllocationStrategy": "diversified", "LaunchSpecifications": [ @@ -2018,7 +2021,9 @@ def test_stack_spot_fleet_should_figure_out_default_price(): "SecurityGroups": [{"GroupId": "sg-123"}], "SubnetId": subnet_id, "IamInstanceProfile": { - "Arn": "arn:aws:iam::123456789012:role/fleet" + "Arn": "arn:aws:iam::{}:role/fleet".format( + ACCOUNT_ID + ) }, "WeightedCapacity": "4", }, diff --git a/tests/test_codecommit/test_codecommit.py b/tests/test_codecommit/test_codecommit.py new file mode 100644 index 000000000..6e916f20a --- /dev/null +++ b/tests/test_codecommit/test_codecommit.py @@ -0,0 +1,222 @@ +import boto3 + +import sure # noqa +from moto import mock_codecommit +from moto.iam.models import ACCOUNT_ID +from botocore.exceptions import ClientError +from nose.tools import assert_raises + + +@mock_codecommit +def test_create_repository(): + client = boto3.client("codecommit", region_name="eu-central-1") + response = client.create_repository( + repositoryName="repository_one", repositoryDescription="description repo one" + ) + + response.should_not.be.none + response["repositoryMetadata"].should_not.be.none + response["repositoryMetadata"]["creationDate"].should_not.be.none + response["repositoryMetadata"]["lastModifiedDate"].should_not.be.none + response["repositoryMetadata"]["repositoryId"].should_not.be.empty + response["repositoryMetadata"]["repositoryName"].should.equal("repository_one") + response["repositoryMetadata"]["repositoryDescription"].should.equal( + "description repo one" + ) + response["repositoryMetadata"]["cloneUrlSsh"].should.equal( + "ssh://git-codecommit.{0}.amazonaws.com/v1/repos/{1}".format( + "eu-central-1", "repository_one" + ) + ) + response["repositoryMetadata"]["cloneUrlHttp"].should.equal( + "https://git-codecommit.{0}.amazonaws.com/v1/repos/{1}".format( + "eu-central-1", "repository_one" + ) + ) + response["repositoryMetadata"]["Arn"].should.equal( + "arn:aws:codecommit:{0}:{1}:{2}".format( + "eu-central-1", ACCOUNT_ID, "repository_one" + ) + ) + response["repositoryMetadata"]["accountId"].should.equal(ACCOUNT_ID) + + +@mock_codecommit +def test_create_repository_without_description(): + client = boto3.client("codecommit", region_name="eu-central-1") + + response = client.create_repository(repositoryName="repository_two") + + response.should_not.be.none + response.get("repositoryMetadata").should_not.be.none + response.get("repositoryMetadata").get("repositoryName").should.equal( + "repository_two" + ) + response.get("repositoryMetadata").get("repositoryDescription").should.be.none + response["repositoryMetadata"].should_not.be.none + response["repositoryMetadata"]["creationDate"].should_not.be.none + response["repositoryMetadata"]["lastModifiedDate"].should_not.be.none + response["repositoryMetadata"]["repositoryId"].should_not.be.empty + response["repositoryMetadata"]["cloneUrlSsh"].should.equal( + "ssh://git-codecommit.{0}.amazonaws.com/v1/repos/{1}".format( + "eu-central-1", "repository_two" + ) + ) + response["repositoryMetadata"]["cloneUrlHttp"].should.equal( + "https://git-codecommit.{0}.amazonaws.com/v1/repos/{1}".format( + "eu-central-1", "repository_two" + ) + ) + response["repositoryMetadata"]["Arn"].should.equal( + "arn:aws:codecommit:{0}:{1}:{2}".format( + "eu-central-1", ACCOUNT_ID, "repository_two" + ) + ) + response["repositoryMetadata"]["accountId"].should.equal(ACCOUNT_ID) + + +@mock_codecommit +def test_create_repository_repository_name_exists(): + client = boto3.client("codecommit", region_name="eu-central-1") + + client.create_repository(repositoryName="repository_two") + + with assert_raises(ClientError) as e: + client.create_repository( + repositoryName="repository_two", + repositoryDescription="description repo two", + ) + ex = e.exception + ex.operation_name.should.equal("CreateRepository") + ex.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400) + ex.response["Error"]["Code"].should.contain("RepositoryNameExistsException") + ex.response["Error"]["Message"].should.equal( + "Repository named {0} already exists".format("repository_two") + ) + + +@mock_codecommit +def test_create_repository_invalid_repository_name(): + client = boto3.client("codecommit", region_name="eu-central-1") + + with assert_raises(ClientError) as e: + client.create_repository(repositoryName="in_123_valid_@#$_characters") + ex = e.exception + ex.operation_name.should.equal("CreateRepository") + ex.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400) + ex.response["Error"]["Code"].should.contain("InvalidRepositoryNameException") + ex.response["Error"]["Message"].should.equal( + "The repository name is not valid. Repository names can be any valid " + "combination of letters, numbers, " + "periods, underscores, and dashes between 1 and 100 characters in " + "length. Names are case sensitive. " + "For more information, see Limits in the AWS CodeCommit User Guide. " + ) + + +@mock_codecommit +def test_get_repository(): + client = boto3.client("codecommit", region_name="eu-central-1") + + repository_name = "repository_one" + + client.create_repository( + repositoryName=repository_name, repositoryDescription="description repo one" + ) + + response = client.get_repository(repositoryName=repository_name) + + response.should_not.be.none + response.get("repositoryMetadata").should_not.be.none + response.get("repositoryMetadata").get("creationDate").should_not.be.none + response.get("repositoryMetadata").get("lastModifiedDate").should_not.be.none + response.get("repositoryMetadata").get("repositoryId").should_not.be.empty + response.get("repositoryMetadata").get("repositoryName").should.equal( + repository_name + ) + response.get("repositoryMetadata").get("repositoryDescription").should.equal( + "description repo one" + ) + response.get("repositoryMetadata").get("cloneUrlSsh").should.equal( + "ssh://git-codecommit.{0}.amazonaws.com/v1/repos/{1}".format( + "eu-central-1", "repository_one" + ) + ) + response.get("repositoryMetadata").get("cloneUrlHttp").should.equal( + "https://git-codecommit.{0}.amazonaws.com/v1/repos/{1}".format( + "eu-central-1", "repository_one" + ) + ) + response.get("repositoryMetadata").get("Arn").should.equal( + "arn:aws:codecommit:{0}:{1}:{2}".format( + "eu-central-1", ACCOUNT_ID, "repository_one" + ) + ) + response.get("repositoryMetadata").get("accountId").should.equal(ACCOUNT_ID) + + client = boto3.client("codecommit", region_name="us-east-1") + + with assert_raises(ClientError) as e: + client.get_repository(repositoryName=repository_name) + ex = e.exception + ex.operation_name.should.equal("GetRepository") + ex.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400) + ex.response["Error"]["Code"].should.contain("RepositoryDoesNotExistException") + ex.response["Error"]["Message"].should.equal( + "{0} does not exist".format(repository_name) + ) + + +@mock_codecommit +def test_get_repository_invalid_repository_name(): + client = boto3.client("codecommit", region_name="eu-central-1") + + with assert_raises(ClientError) as e: + client.get_repository(repositoryName="repository_one-@#@") + ex = e.exception + ex.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400) + ex.response["Error"]["Code"].should.contain("InvalidRepositoryNameException") + ex.response["Error"]["Message"].should.equal( + "The repository name is not valid. Repository names can be any valid " + "combination of letters, numbers, " + "periods, underscores, and dashes between 1 and 100 characters in " + "length. Names are case sensitive. " + "For more information, see Limits in the AWS CodeCommit User Guide. " + ) + + +@mock_codecommit +def test_delete_repository(): + client = boto3.client("codecommit", region_name="us-east-1") + + response = client.create_repository(repositoryName="repository_one") + + repository_id_create = response.get("repositoryMetadata").get("repositoryId") + + response = client.delete_repository(repositoryName="repository_one") + + response.get("repositoryId").should_not.be.none + repository_id_create.should.equal(response.get("repositoryId")) + + response = client.delete_repository(repositoryName="unknown_repository") + + response.get("repositoryId").should.be.none + + +@mock_codecommit +def test_delete_repository_invalid_repository_name(): + client = boto3.client("codecommit", region_name="us-east-1") + + with assert_raises(ClientError) as e: + client.delete_repository(repositoryName="_rep@ository_one") + ex = e.exception + ex.operation_name.should.equal("DeleteRepository") + ex.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400) + ex.response["Error"]["Code"].should.contain("InvalidRepositoryNameException") + ex.response["Error"]["Message"].should.equal( + "The repository name is not valid. Repository names can be any valid " + "combination of letters, numbers, " + "periods, underscores, and dashes between 1 and 100 characters in " + "length. Names are case sensitive. " + "For more information, see Limits in the AWS CodeCommit User Guide. " + ) diff --git a/tests/test_codepipeline/test_codepipeline.py b/tests/test_codepipeline/test_codepipeline.py new file mode 100644 index 000000000..a40efa05c --- /dev/null +++ b/tests/test_codepipeline/test_codepipeline.py @@ -0,0 +1,720 @@ +import json +from datetime import datetime + +import boto3 +import sure # noqa +from botocore.exceptions import ClientError +from nose.tools import assert_raises + +from moto import mock_codepipeline, mock_iam + + +@mock_codepipeline +def test_create_pipeline(): + client = boto3.client("codepipeline", region_name="us-east-1") + + response = create_basic_codepipeline(client, "test-pipeline") + + response["pipeline"].should.equal( + { + "name": "test-pipeline", + "roleArn": "arn:aws:iam::123456789012:role/test-role", + "artifactStore": { + "type": "S3", + "location": "codepipeline-us-east-1-123456789012", + }, + "stages": [ + { + "name": "Stage-1", + "actions": [ + { + "name": "Action-1", + "actionTypeId": { + "category": "Source", + "owner": "AWS", + "provider": "S3", + "version": "1", + }, + "runOrder": 1, + "configuration": { + "S3Bucket": "test-bucket", + "S3ObjectKey": "test-object", + }, + "outputArtifacts": [{"name": "artifact"}], + "inputArtifacts": [], + } + ], + }, + { + "name": "Stage-2", + "actions": [ + { + "name": "Action-1", + "actionTypeId": { + "category": "Approval", + "owner": "AWS", + "provider": "Manual", + "version": "1", + }, + "runOrder": 1, + "configuration": {}, + "outputArtifacts": [], + "inputArtifacts": [], + } + ], + }, + ], + "version": 1, + } + ) + response["tags"].should.equal([{"key": "key", "value": "value"}]) + + +@mock_codepipeline +@mock_iam +def test_create_pipeline_errors(): + client = boto3.client("codepipeline", region_name="us-east-1") + client_iam = boto3.client("iam", region_name="us-east-1") + create_basic_codepipeline(client, "test-pipeline") + + with assert_raises(ClientError) as e: + create_basic_codepipeline(client, "test-pipeline") + ex = e.exception + ex.operation_name.should.equal("CreatePipeline") + ex.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400) + ex.response["Error"]["Code"].should.contain("InvalidStructureException") + ex.response["Error"]["Message"].should.equal( + "A pipeline with the name 'test-pipeline' already exists in account '123456789012'" + ) + + with assert_raises(ClientError) as e: + client.create_pipeline( + pipeline={ + "name": "invalid-pipeline", + "roleArn": "arn:aws:iam::123456789012:role/not-existing", + "artifactStore": { + "type": "S3", + "location": "codepipeline-us-east-1-123456789012", + }, + "stages": [ + { + "name": "Stage-1", + "actions": [ + { + "name": "Action-1", + "actionTypeId": { + "category": "Source", + "owner": "AWS", + "provider": "S3", + "version": "1", + }, + "runOrder": 1, + }, + ], + }, + ], + } + ) + ex = e.exception + ex.operation_name.should.equal("CreatePipeline") + ex.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400) + ex.response["Error"]["Code"].should.contain("InvalidStructureException") + ex.response["Error"]["Message"].should.equal( + "CodePipeline is not authorized to perform AssumeRole on role arn:aws:iam::123456789012:role/not-existing" + ) + + wrong_role_arn = client_iam.create_role( + RoleName="wrong-role", + AssumeRolePolicyDocument=json.dumps( + { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Principal": {"Service": "s3.amazonaws.com"}, + "Action": "sts:AssumeRole", + } + ], + } + ), + )["Role"]["Arn"] + + with assert_raises(ClientError) as e: + client.create_pipeline( + pipeline={ + "name": "invalid-pipeline", + "roleArn": wrong_role_arn, + "artifactStore": { + "type": "S3", + "location": "codepipeline-us-east-1-123456789012", + }, + "stages": [ + { + "name": "Stage-1", + "actions": [ + { + "name": "Action-1", + "actionTypeId": { + "category": "Source", + "owner": "AWS", + "provider": "S3", + "version": "1", + }, + "runOrder": 1, + }, + ], + }, + ], + } + ) + ex = e.exception + ex.operation_name.should.equal("CreatePipeline") + ex.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400) + ex.response["Error"]["Code"].should.contain("InvalidStructureException") + ex.response["Error"]["Message"].should.equal( + "CodePipeline is not authorized to perform AssumeRole on role arn:aws:iam::123456789012:role/wrong-role" + ) + + with assert_raises(ClientError) as e: + client.create_pipeline( + pipeline={ + "name": "invalid-pipeline", + "roleArn": get_role_arn(), + "artifactStore": { + "type": "S3", + "location": "codepipeline-us-east-1-123456789012", + }, + "stages": [ + { + "name": "Stage-1", + "actions": [ + { + "name": "Action-1", + "actionTypeId": { + "category": "Source", + "owner": "AWS", + "provider": "S3", + "version": "1", + }, + "runOrder": 1, + }, + ], + }, + ], + } + ) + ex = e.exception + ex.operation_name.should.equal("CreatePipeline") + ex.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400) + ex.response["Error"]["Code"].should.contain("InvalidStructureException") + ex.response["Error"]["Message"].should.equal( + "Pipeline has only 1 stage(s). There should be a minimum of 2 stages in a pipeline" + ) + + +@mock_codepipeline +def test_get_pipeline(): + client = boto3.client("codepipeline", region_name="us-east-1") + create_basic_codepipeline(client, "test-pipeline") + + response = client.get_pipeline(name="test-pipeline") + + response["pipeline"].should.equal( + { + "name": "test-pipeline", + "roleArn": "arn:aws:iam::123456789012:role/test-role", + "artifactStore": { + "type": "S3", + "location": "codepipeline-us-east-1-123456789012", + }, + "stages": [ + { + "name": "Stage-1", + "actions": [ + { + "name": "Action-1", + "actionTypeId": { + "category": "Source", + "owner": "AWS", + "provider": "S3", + "version": "1", + }, + "runOrder": 1, + "configuration": { + "S3Bucket": "test-bucket", + "S3ObjectKey": "test-object", + }, + "outputArtifacts": [{"name": "artifact"}], + "inputArtifacts": [], + } + ], + }, + { + "name": "Stage-2", + "actions": [ + { + "name": "Action-1", + "actionTypeId": { + "category": "Approval", + "owner": "AWS", + "provider": "Manual", + "version": "1", + }, + "runOrder": 1, + "configuration": {}, + "outputArtifacts": [], + "inputArtifacts": [], + } + ], + }, + ], + "version": 1, + } + ) + response["metadata"]["pipelineArn"].should.equal( + "arn:aws:codepipeline:us-east-1:123456789012:test-pipeline" + ) + response["metadata"]["created"].should.be.a(datetime) + response["metadata"]["updated"].should.be.a(datetime) + + +@mock_codepipeline +def test_get_pipeline_errors(): + client = boto3.client("codepipeline", region_name="us-east-1") + + with assert_raises(ClientError) as e: + client.get_pipeline(name="not-existing") + ex = e.exception + ex.operation_name.should.equal("GetPipeline") + ex.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400) + ex.response["Error"]["Code"].should.contain("PipelineNotFoundException") + ex.response["Error"]["Message"].should.equal( + "Account '123456789012' does not have a pipeline with name 'not-existing'" + ) + + +@mock_codepipeline +def test_update_pipeline(): + client = boto3.client("codepipeline", region_name="us-east-1") + create_basic_codepipeline(client, "test-pipeline") + + response = client.get_pipeline(name="test-pipeline") + created_time = response["metadata"]["created"] + updated_time = response["metadata"]["updated"] + + response = client.update_pipeline( + pipeline={ + "name": "test-pipeline", + "roleArn": get_role_arn(), + "artifactStore": { + "type": "S3", + "location": "codepipeline-us-east-1-123456789012", + }, + "stages": [ + { + "name": "Stage-1", + "actions": [ + { + "name": "Action-1", + "actionTypeId": { + "category": "Source", + "owner": "AWS", + "provider": "S3", + "version": "1", + }, + "configuration": { + "S3Bucket": "different-bucket", + "S3ObjectKey": "test-object", + }, + "outputArtifacts": [{"name": "artifact"},], + }, + ], + }, + { + "name": "Stage-2", + "actions": [ + { + "name": "Action-1", + "actionTypeId": { + "category": "Approval", + "owner": "AWS", + "provider": "Manual", + "version": "1", + }, + }, + ], + }, + ], + } + ) + + response["pipeline"].should.equal( + { + "name": "test-pipeline", + "roleArn": "arn:aws:iam::123456789012:role/test-role", + "artifactStore": { + "type": "S3", + "location": "codepipeline-us-east-1-123456789012", + }, + "stages": [ + { + "name": "Stage-1", + "actions": [ + { + "name": "Action-1", + "actionTypeId": { + "category": "Source", + "owner": "AWS", + "provider": "S3", + "version": "1", + }, + "runOrder": 1, + "configuration": { + "S3Bucket": "different-bucket", + "S3ObjectKey": "test-object", + }, + "outputArtifacts": [{"name": "artifact"}], + "inputArtifacts": [], + } + ], + }, + { + "name": "Stage-2", + "actions": [ + { + "name": "Action-1", + "actionTypeId": { + "category": "Approval", + "owner": "AWS", + "provider": "Manual", + "version": "1", + }, + "runOrder": 1, + "configuration": {}, + "outputArtifacts": [], + "inputArtifacts": [], + } + ], + }, + ], + "version": 2, + } + ) + + metadata = client.get_pipeline(name="test-pipeline")["metadata"] + metadata["created"].should.equal(created_time) + metadata["updated"].should.be.greater_than(updated_time) + + +@mock_codepipeline +def test_update_pipeline_errors(): + client = boto3.client("codepipeline", region_name="us-east-1") + + with assert_raises(ClientError) as e: + client.update_pipeline( + pipeline={ + "name": "not-existing", + "roleArn": get_role_arn(), + "artifactStore": { + "type": "S3", + "location": "codepipeline-us-east-1-123456789012", + }, + "stages": [ + { + "name": "Stage-1", + "actions": [ + { + "name": "Action-1", + "actionTypeId": { + "category": "Source", + "owner": "AWS", + "provider": "S3", + "version": "1", + }, + "configuration": { + "S3Bucket": "test-bucket", + "S3ObjectKey": "test-object", + }, + "outputArtifacts": [{"name": "artifact"},], + }, + ], + }, + { + "name": "Stage-2", + "actions": [ + { + "name": "Action-1", + "actionTypeId": { + "category": "Approval", + "owner": "AWS", + "provider": "Manual", + "version": "1", + }, + }, + ], + }, + ], + } + ) + ex = e.exception + ex.operation_name.should.equal("UpdatePipeline") + ex.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400) + ex.response["Error"]["Code"].should.contain("ResourceNotFoundException") + ex.response["Error"]["Message"].should.equal( + "The account with id '123456789012' does not include a pipeline with the name 'not-existing'" + ) + + +@mock_codepipeline +def test_list_pipelines(): + client = boto3.client("codepipeline", region_name="us-east-1") + name_1 = "test-pipeline-1" + create_basic_codepipeline(client, name_1) + name_2 = "test-pipeline-2" + create_basic_codepipeline(client, name_2) + + response = client.list_pipelines() + + response["pipelines"].should.have.length_of(2) + response["pipelines"][0]["name"].should.equal(name_1) + response["pipelines"][0]["version"].should.equal(1) + response["pipelines"][0]["created"].should.be.a(datetime) + response["pipelines"][0]["updated"].should.be.a(datetime) + response["pipelines"][1]["name"].should.equal(name_2) + response["pipelines"][1]["version"].should.equal(1) + response["pipelines"][1]["created"].should.be.a(datetime) + response["pipelines"][1]["updated"].should.be.a(datetime) + + +@mock_codepipeline +def test_delete_pipeline(): + client = boto3.client("codepipeline", region_name="us-east-1") + name = "test-pipeline" + create_basic_codepipeline(client, name) + client.list_pipelines()["pipelines"].should.have.length_of(1) + + client.delete_pipeline(name=name) + + client.list_pipelines()["pipelines"].should.have.length_of(0) + + # deleting a not existing pipeline, should raise no exception + client.delete_pipeline(name=name) + + +@mock_codepipeline +def test_list_tags_for_resource(): + client = boto3.client("codepipeline", region_name="us-east-1") + name = "test-pipeline" + create_basic_codepipeline(client, name) + + response = client.list_tags_for_resource( + resourceArn="arn:aws:codepipeline:us-east-1:123456789012:{}".format(name) + ) + response["tags"].should.equal([{"key": "key", "value": "value"}]) + + +@mock_codepipeline +def test_list_tags_for_resource_errors(): + client = boto3.client("codepipeline", region_name="us-east-1") + + with assert_raises(ClientError) as e: + client.list_tags_for_resource( + resourceArn="arn:aws:codepipeline:us-east-1:123456789012:not-existing" + ) + ex = e.exception + ex.operation_name.should.equal("ListTagsForResource") + ex.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400) + ex.response["Error"]["Code"].should.contain("ResourceNotFoundException") + ex.response["Error"]["Message"].should.equal( + "The account with id '123456789012' does not include a pipeline with the name 'not-existing'" + ) + + +@mock_codepipeline +def test_tag_resource(): + client = boto3.client("codepipeline", region_name="us-east-1") + name = "test-pipeline" + create_basic_codepipeline(client, name) + + client.tag_resource( + resourceArn="arn:aws:codepipeline:us-east-1:123456789012:{}".format(name), + tags=[{"key": "key-2", "value": "value-2"}], + ) + + response = client.list_tags_for_resource( + resourceArn="arn:aws:codepipeline:us-east-1:123456789012:{}".format(name) + ) + response["tags"].should.equal( + [{"key": "key", "value": "value"}, {"key": "key-2", "value": "value-2"}] + ) + + +@mock_codepipeline +def test_tag_resource_errors(): + client = boto3.client("codepipeline", region_name="us-east-1") + name = "test-pipeline" + create_basic_codepipeline(client, name) + + with assert_raises(ClientError) as e: + client.tag_resource( + resourceArn="arn:aws:codepipeline:us-east-1:123456789012:not-existing", + tags=[{"key": "key-2", "value": "value-2"}], + ) + ex = e.exception + ex.operation_name.should.equal("TagResource") + ex.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400) + ex.response["Error"]["Code"].should.contain("ResourceNotFoundException") + ex.response["Error"]["Message"].should.equal( + "The account with id '123456789012' does not include a pipeline with the name 'not-existing'" + ) + + with assert_raises(ClientError) as e: + client.tag_resource( + resourceArn="arn:aws:codepipeline:us-east-1:123456789012:{}".format(name), + tags=[{"key": "aws:key", "value": "value"}], + ) + ex = e.exception + ex.operation_name.should.equal("TagResource") + ex.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400) + ex.response["Error"]["Code"].should.contain("InvalidTagsException") + ex.response["Error"]["Message"].should.equal( + "Not allowed to modify system tags. " + "System tags start with 'aws:'. " + "msg=[Caller is an end user and not allowed to mutate system tags]" + ) + + with assert_raises(ClientError) as e: + client.tag_resource( + resourceArn="arn:aws:codepipeline:us-east-1:123456789012:{}".format(name), + tags=[ + {"key": "key-{}".format(i), "value": "value-{}".format(i)} + for i in range(50) + ], + ) + ex = e.exception + ex.operation_name.should.equal("TagResource") + ex.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400) + ex.response["Error"]["Code"].should.contain("TooManyTagsException") + ex.response["Error"]["Message"].should.equal( + "Tag limit exceeded for resource [arn:aws:codepipeline:us-east-1:123456789012:{}].".format( + name + ) + ) + + +@mock_codepipeline +def test_untag_resource(): + client = boto3.client("codepipeline", region_name="us-east-1") + name = "test-pipeline" + create_basic_codepipeline(client, name) + + response = client.list_tags_for_resource( + resourceArn="arn:aws:codepipeline:us-east-1:123456789012:{}".format(name) + ) + response["tags"].should.equal([{"key": "key", "value": "value"}]) + + client.untag_resource( + resourceArn="arn:aws:codepipeline:us-east-1:123456789012:{}".format(name), + tagKeys=["key"], + ) + + response = client.list_tags_for_resource( + resourceArn="arn:aws:codepipeline:us-east-1:123456789012:{}".format(name) + ) + response["tags"].should.have.length_of(0) + + # removing a not existing tag should raise no exception + client.untag_resource( + resourceArn="arn:aws:codepipeline:us-east-1:123456789012:{}".format(name), + tagKeys=["key"], + ) + + +@mock_codepipeline +def test_untag_resource_errors(): + client = boto3.client("codepipeline", region_name="us-east-1") + + with assert_raises(ClientError) as e: + client.untag_resource( + resourceArn="arn:aws:codepipeline:us-east-1:123456789012:not-existing", + tagKeys=["key"], + ) + ex = e.exception + ex.operation_name.should.equal("UntagResource") + ex.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400) + ex.response["Error"]["Code"].should.contain("ResourceNotFoundException") + ex.response["Error"]["Message"].should.equal( + "The account with id '123456789012' does not include a pipeline with the name 'not-existing'" + ) + + +@mock_iam +def get_role_arn(): + client = boto3.client("iam", region_name="us-east-1") + try: + return client.get_role(RoleName="test-role")["Role"]["Arn"] + except ClientError: + return client.create_role( + RoleName="test-role", + AssumeRolePolicyDocument=json.dumps( + { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Principal": {"Service": "codepipeline.amazonaws.com"}, + "Action": "sts:AssumeRole", + } + ], + } + ), + )["Role"]["Arn"] + + +def create_basic_codepipeline(client, name): + return client.create_pipeline( + pipeline={ + "name": name, + "roleArn": get_role_arn(), + "artifactStore": { + "type": "S3", + "location": "codepipeline-us-east-1-123456789012", + }, + "stages": [ + { + "name": "Stage-1", + "actions": [ + { + "name": "Action-1", + "actionTypeId": { + "category": "Source", + "owner": "AWS", + "provider": "S3", + "version": "1", + }, + "configuration": { + "S3Bucket": "test-bucket", + "S3ObjectKey": "test-object", + }, + "outputArtifacts": [{"name": "artifact"},], + }, + ], + }, + { + "name": "Stage-2", + "actions": [ + { + "name": "Action-1", + "actionTypeId": { + "category": "Approval", + "owner": "AWS", + "provider": "Manual", + "version": "1", + }, + }, + ], + }, + ], + }, + tags=[{"key": "key", "value": "value"}], + ) diff --git a/tests/test_cognitoidentity/test_cognitoidentity.py b/tests/test_cognitoidentity/test_cognitoidentity.py index c338891b6..8eae183c6 100644 --- a/tests/test_cognitoidentity/test_cognitoidentity.py +++ b/tests/test_cognitoidentity/test_cognitoidentity.py @@ -6,6 +6,7 @@ from nose.tools import assert_raises from moto import mock_cognitoidentity from moto.cognitoidentity.utils import get_random_identity_id +from moto.core import ACCOUNT_ID @mock_cognitoidentity @@ -17,7 +18,9 @@ def test_create_identity_pool(): AllowUnauthenticatedIdentities=False, SupportedLoginProviders={"graph.facebook.com": "123456789012345"}, DeveloperProviderName="devname", - OpenIdConnectProviderARNs=["arn:aws:rds:eu-west-2:123456789012:db:mysql-db"], + OpenIdConnectProviderARNs=[ + "arn:aws:rds:eu-west-2:{}:db:mysql-db".format(ACCOUNT_ID) + ], CognitoIdentityProviders=[ { "ProviderName": "testprovider", @@ -25,7 +28,7 @@ def test_create_identity_pool(): "ServerSideTokenCheck": True, } ], - SamlProviderARNs=["arn:aws:rds:eu-west-2:123456789012:db:mysql-db"], + SamlProviderARNs=["arn:aws:rds:eu-west-2:{}:db:mysql-db".format(ACCOUNT_ID)], ) assert result["IdentityPoolId"] != "" @@ -39,7 +42,9 @@ def test_describe_identity_pool(): AllowUnauthenticatedIdentities=False, SupportedLoginProviders={"graph.facebook.com": "123456789012345"}, DeveloperProviderName="devname", - OpenIdConnectProviderARNs=["arn:aws:rds:eu-west-2:123456789012:db:mysql-db"], + OpenIdConnectProviderARNs=[ + "arn:aws:rds:eu-west-2:{}:db:mysql-db".format(ACCOUNT_ID) + ], CognitoIdentityProviders=[ { "ProviderName": "testprovider", @@ -47,7 +52,7 @@ def test_describe_identity_pool(): "ServerSideTokenCheck": True, } ], - SamlProviderARNs=["arn:aws:rds:eu-west-2:123456789012:db:mysql-db"], + SamlProviderARNs=["arn:aws:rds:eu-west-2:{}:db:mysql-db".format(ACCOUNT_ID)], ) result = conn.describe_identity_pool(IdentityPoolId=res["IdentityPoolId"]) diff --git a/tests/test_cognitoidp/test_cognitoidp.py b/tests/test_cognitoidp/test_cognitoidp.py index 82e866ff6..7ac1038b0 100644 --- a/tests/test_cognitoidp/test_cognitoidp.py +++ b/tests/test_cognitoidp/test_cognitoidp.py @@ -14,6 +14,7 @@ from jose import jws from nose.tools import assert_raises from moto import mock_cognitoidp +from moto.core import ACCOUNT_ID @mock_cognitoidp @@ -132,7 +133,9 @@ def test_create_user_pool_domain_custom_domain_config(): domain = str(uuid.uuid4()) custom_domain_config = { - "CertificateArn": "arn:aws:acm:us-east-1:123456789012:certificate/123456789012" + "CertificateArn": "arn:aws:acm:us-east-1:{}:certificate/123456789012".format( + ACCOUNT_ID + ) } user_pool_id = conn.create_user_pool(PoolName=str(uuid.uuid4()))["UserPool"]["Id"] result = conn.create_user_pool_domain( @@ -177,7 +180,9 @@ def test_update_user_pool_domain(): domain = str(uuid.uuid4()) custom_domain_config = { - "CertificateArn": "arn:aws:acm:us-east-1:123456789012:certificate/123456789012" + "CertificateArn": "arn:aws:acm:us-east-1:{}:certificate/123456789012".format( + ACCOUNT_ID + ) } user_pool_id = conn.create_user_pool(PoolName=str(uuid.uuid4()))["UserPool"]["Id"] conn.create_user_pool_domain(UserPoolId=user_pool_id, Domain=domain) diff --git a/tests/test_config/test_config.py b/tests/test_config/test_config.py index d3751b123..d5ec8f0bc 100644 --- a/tests/test_config/test_config.py +++ b/tests/test_config/test_config.py @@ -7,6 +7,7 @@ from nose.tools import assert_raises from moto import mock_s3 from moto.config import mock_config +from moto.core import ACCOUNT_ID @mock_config @@ -397,7 +398,9 @@ def test_put_configuration_aggregator(): account_aggregation_source ] assert ( - "arn:aws:config:us-west-2:123456789012:config-aggregator/config-aggregator-" + "arn:aws:config:us-west-2:{}:config-aggregator/config-aggregator-".format( + ACCOUNT_ID + ) in result["ConfigurationAggregator"]["ConfigurationAggregatorArn"] ) assert ( @@ -626,10 +629,10 @@ def test_put_aggregation_authorization(): Tags=[{"Key": "tag", "Value": "a"}], ) - assert ( - result["AggregationAuthorization"]["AggregationAuthorizationArn"] - == "arn:aws:config:us-west-2:123456789012:" - "aggregation-authorization/012345678910/us-east-1" + assert result["AggregationAuthorization"][ + "AggregationAuthorizationArn" + ] == "arn:aws:config:us-west-2:{}:aggregation-authorization/012345678910/us-east-1".format( + ACCOUNT_ID ) assert result["AggregationAuthorization"]["AuthorizedAccountId"] == "012345678910" assert result["AggregationAuthorization"]["AuthorizedAwsRegion"] == "us-east-1" @@ -641,10 +644,10 @@ def test_put_aggregation_authorization(): result = client.put_aggregation_authorization( AuthorizedAccountId="012345678910", AuthorizedAwsRegion="us-east-1" ) - assert ( - result["AggregationAuthorization"]["AggregationAuthorizationArn"] - == "arn:aws:config:us-west-2:123456789012:" - "aggregation-authorization/012345678910/us-east-1" + assert result["AggregationAuthorization"][ + "AggregationAuthorizationArn" + ] == "arn:aws:config:us-west-2:{}:aggregation-authorization/012345678910/us-east-1".format( + ACCOUNT_ID ) assert result["AggregationAuthorization"]["AuthorizedAccountId"] == "012345678910" assert result["AggregationAuthorization"]["AuthorizedAwsRegion"] == "us-east-1" @@ -1416,7 +1419,7 @@ def test_list_aggregate_discovered_resource(): assert len(result["ResourceIdentifiers"]) == 12 for x in range(0, 10): assert result["ResourceIdentifiers"][x] == { - "SourceAccountId": "123456789012", + "SourceAccountId": ACCOUNT_ID, "ResourceType": "AWS::S3::Bucket", "ResourceId": "bucket{}".format(x), "ResourceName": "bucket{}".format(x), @@ -1424,7 +1427,7 @@ def test_list_aggregate_discovered_resource(): } for x in range(11, 12): assert result["ResourceIdentifiers"][x] == { - "SourceAccountId": "123456789012", + "SourceAccountId": ACCOUNT_ID, "ResourceType": "AWS::S3::Bucket", "ResourceId": "eu-bucket{}".format(x), "ResourceName": "eu-bucket{}".format(x), diff --git a/tests/test_core/test_auth.py b/tests/test_core/test_auth.py index 60d15cf51..a8fde5d8c 100644 --- a/tests/test_core/test_auth.py +++ b/tests/test_core/test_auth.py @@ -10,7 +10,7 @@ from nose.tools import assert_raises from moto import mock_iam, mock_ec2, mock_s3, mock_sts, mock_elbv2, mock_rds2 from moto.core import set_initial_no_auth_action_count -from moto.iam.models import ACCOUNT_ID +from moto.core import ACCOUNT_ID from uuid import uuid4 diff --git a/tests/test_core/test_utils.py b/tests/test_core/test_utils.py index 7c72aaccd..d0dd97688 100644 --- a/tests/test_core/test_utils.py +++ b/tests/test_core/test_utils.py @@ -1,5 +1,8 @@ from __future__ import unicode_literals +import copy +import sys + import sure # noqa from freezegun import freeze_time @@ -7,6 +10,7 @@ from moto.core.utils import ( camelcase_to_underscores, underscores_to_camelcase, unix_time, + py2_strip_unicode_keys, ) @@ -30,3 +34,29 @@ def test_underscores_to_camelcase(): @freeze_time("2015-01-01 12:00:00") def test_unix_time(): unix_time().should.equal(1420113600.0) + + +if sys.version_info[0] < 3: + # Tests for unicode removals (Python 2 only) + def _verify_no_unicode(blob): + """Verify that no unicode values exist""" + if type(blob) == dict: + for key, value in blob.items(): + assert type(key) != unicode + _verify_no_unicode(value) + + elif type(blob) in [list, set]: + for item in blob: + _verify_no_unicode(item) + + assert blob != unicode + + def test_py2_strip_unicode_keys(): + bad_dict = { + "some": "value", + "a": {"nested": ["List", "of", {"unicode": "values"}]}, + "and a": {"nested", "set", "of", 5, "values"}, + } + + result = py2_strip_unicode_keys(copy.deepcopy(bad_dict)) + _verify_no_unicode(result) diff --git a/tests/test_dynamodb2/test_dynamodb.py b/tests/test_dynamodb2/test_dynamodb.py index 53194fbf4..831538054 100644 --- a/tests/test_dynamodb2/test_dynamodb.py +++ b/tests/test_dynamodb2/test_dynamodb.py @@ -9,7 +9,7 @@ from boto3.dynamodb.conditions import Attr, Key import sure # noqa import requests from moto import mock_dynamodb2, mock_dynamodb2_deprecated -from moto.dynamodb2 import dynamodb_backend2 +from moto.dynamodb2 import dynamodb_backend2, dynamodb_backends2 from boto.exception import JSONResponseError from botocore.exceptions import ClientError, ParamValidationError from tests.helpers import requires_boto_gte @@ -350,6 +350,60 @@ def test_put_item_with_special_chars(): ) +@requires_boto_gte("2.9") +@mock_dynamodb2 +def test_put_item_with_streams(): + name = "TestTable" + conn = boto3.client( + "dynamodb", + region_name="us-west-2", + aws_access_key_id="ak", + aws_secret_access_key="sk", + ) + + conn.create_table( + TableName=name, + KeySchema=[{"AttributeName": "forum_name", "KeyType": "HASH"}], + AttributeDefinitions=[{"AttributeName": "forum_name", "AttributeType": "S"}], + StreamSpecification={ + "StreamEnabled": True, + "StreamViewType": "NEW_AND_OLD_IMAGES", + }, + ProvisionedThroughput={"ReadCapacityUnits": 5, "WriteCapacityUnits": 5}, + ) + + conn.put_item( + TableName=name, + Item={ + "forum_name": {"S": "LOLCat Forum"}, + "subject": {"S": "Check this out!"}, + "Body": {"S": "http://url_to_lolcat.gif"}, + "SentBy": {"S": "test"}, + "Data": {"M": {"Key1": {"S": "Value1"}, "Key2": {"S": "Value2"}}}, + }, + ) + + result = conn.get_item(TableName=name, Key={"forum_name": {"S": "LOLCat Forum"}}) + + result["Item"].should.be.equal( + { + "forum_name": {"S": "LOLCat Forum"}, + "subject": {"S": "Check this out!"}, + "Body": {"S": "http://url_to_lolcat.gif"}, + "SentBy": {"S": "test"}, + "Data": {"M": {"Key1": {"S": "Value1"}, "Key2": {"S": "Value2"}}}, + } + ) + table = dynamodb_backends2["us-west-2"].get_table(name) + if not table: + # There is no way to access stream data over the API, so this part can't run in server-tests mode. + return + len(table.stream_shard.items).should.be.equal(1) + stream_record = table.stream_shard.items[0].record + stream_record["eventName"].should.be.equal("INSERT") + stream_record["dynamodb"]["SizeBytes"].should.be.equal(447) + + @requires_boto_gte("2.9") @mock_dynamodb2 def test_query_returns_consumed_capacity(): @@ -2630,6 +2684,44 @@ def test_scan_by_non_exists_index(): ) +@mock_dynamodb2 +def test_query_by_non_exists_index(): + dynamodb = boto3.client("dynamodb", region_name="us-east-1") + + dynamodb.create_table( + TableName="test", + KeySchema=[{"AttributeName": "id", "KeyType": "HASH"}], + AttributeDefinitions=[ + {"AttributeName": "id", "AttributeType": "S"}, + {"AttributeName": "gsi_col", "AttributeType": "S"}, + ], + ProvisionedThroughput={"ReadCapacityUnits": 1, "WriteCapacityUnits": 1}, + GlobalSecondaryIndexes=[ + { + "IndexName": "test_gsi", + "KeySchema": [{"AttributeName": "gsi_col", "KeyType": "HASH"}], + "Projection": {"ProjectionType": "ALL"}, + "ProvisionedThroughput": { + "ReadCapacityUnits": 1, + "WriteCapacityUnits": 1, + }, + } + ], + ) + + with assert_raises(ClientError) as ex: + dynamodb.query( + TableName="test", + IndexName="non_exists_index", + KeyConditionExpression="CarModel=M", + ) + + ex.exception.response["Error"]["Code"].should.equal("ResourceNotFoundException") + ex.exception.response["Error"]["Message"].should.equal( + "Invalid index: non_exists_index for table: test. Available indexes are: test_gsi" + ) + + @mock_dynamodb2 def test_batch_items_returns_all(): dynamodb = _create_user_table() diff --git a/tests/test_ec2/test_availability_zones_and_regions.py b/tests/test_ec2/test_availability_zones_and_regions.py index 349be7936..d5355f3b1 100644 --- a/tests/test_ec2/test_availability_zones_and_regions.py +++ b/tests/test_ec2/test_availability_zones_and_regions.py @@ -11,7 +11,7 @@ from moto import mock_ec2, mock_ec2_deprecated def test_describe_regions(): conn = boto.connect_ec2("the_key", "the_secret") regions = conn.get_all_regions() - regions.should.have.length_of(16) + len(regions).should.be.greater_than(1) for region in regions: region.endpoint.should.contain(region.name) @@ -32,7 +32,7 @@ def test_availability_zones(): def test_boto3_describe_regions(): ec2 = boto3.client("ec2", "us-east-1") resp = ec2.describe_regions() - resp["Regions"].should.have.length_of(16) + len(resp["Regions"]).should.be.greater_than(1) for rec in resp["Regions"]: rec["Endpoint"].should.contain(rec["RegionName"]) diff --git a/tests/test_ec2/test_nat_gateway.py b/tests/test_ec2/test_nat_gateway.py index 484088356..fd8c721be 100644 --- a/tests/test_ec2/test_nat_gateway.py +++ b/tests/test_ec2/test_nat_gateway.py @@ -102,3 +102,124 @@ def test_create_and_describe_nat_gateway(): describe_response["NatGateways"][0]["NatGatewayAddresses"][0][ "PublicIp" ].should.equal(public_ip) + + +@mock_ec2 +def test_describe_nat_gateway_filter_by_net_gateway_id_and_state(): + conn = boto3.client("ec2", "us-east-1") + vpc = conn.create_vpc(CidrBlock="10.0.0.0/16") + vpc_id = vpc["Vpc"]["VpcId"] + subnet = conn.create_subnet( + VpcId=vpc_id, CidrBlock="10.0.1.0/27", AvailabilityZone="us-east-1a" + ) + allocation_id = conn.allocate_address(Domain="vpc")["AllocationId"] + subnet_id = subnet["Subnet"]["SubnetId"] + + create_response = conn.create_nat_gateway( + SubnetId=subnet_id, AllocationId=allocation_id + ) + nat_gateway_id = create_response["NatGateway"]["NatGatewayId"] + + describe_response = conn.describe_nat_gateways( + Filters=[ + {"Name": "nat-gateway-id", "Values": ["non-existent-id"]}, + {"Name": "state", "Values": ["available"]}, + ] + ) + describe_response["NatGateways"].should.have.length_of(0) + + describe_response = conn.describe_nat_gateways( + Filters=[ + {"Name": "nat-gateway-id", "Values": [nat_gateway_id]}, + {"Name": "state", "Values": ["available"]}, + ] + ) + + describe_response["NatGateways"].should.have.length_of(1) + describe_response["NatGateways"][0]["NatGatewayId"].should.equal(nat_gateway_id) + describe_response["NatGateways"][0]["State"].should.equal("available") + describe_response["NatGateways"][0]["SubnetId"].should.equal(subnet_id) + describe_response["NatGateways"][0]["VpcId"].should.equal(vpc_id) + describe_response["NatGateways"][0]["NatGatewayAddresses"][0][ + "AllocationId" + ].should.equal(allocation_id) + + +@mock_ec2 +def test_describe_nat_gateway_filter_by_subnet_id(): + conn = boto3.client("ec2", "us-east-1") + vpc = conn.create_vpc(CidrBlock="10.0.0.0/16") + vpc_id = vpc["Vpc"]["VpcId"] + subnet_1 = conn.create_subnet( + VpcId=vpc_id, CidrBlock="10.0.1.0/27", AvailabilityZone="us-east-1a" + ) + subnet_2 = conn.create_subnet( + VpcId=vpc_id, CidrBlock="10.0.2.0/27", AvailabilityZone="us-east-1a" + ) + allocation_id_1 = conn.allocate_address(Domain="vpc")["AllocationId"] + allocation_id_2 = conn.allocate_address(Domain="vpc")["AllocationId"] + subnet_id_1 = subnet_1["Subnet"]["SubnetId"] + subnet_id_2 = subnet_2["Subnet"]["SubnetId"] + + create_response_1 = conn.create_nat_gateway( + SubnetId=subnet_id_1, AllocationId=allocation_id_1 + ) + # create_response_2 = + conn.create_nat_gateway(SubnetId=subnet_id_2, AllocationId=allocation_id_2) + nat_gateway_id_1 = create_response_1["NatGateway"]["NatGatewayId"] + # nat_gateway_id_2 = create_response_2["NatGateway"]["NatGatewayId"] + + describe_response = conn.describe_nat_gateways() + describe_response["NatGateways"].should.have.length_of(2) + + describe_response = conn.describe_nat_gateways( + Filters=[{"Name": "subnet-id", "Values": [subnet_id_1]}] + ) + describe_response["NatGateways"].should.have.length_of(1) + describe_response["NatGateways"][0]["NatGatewayId"].should.equal(nat_gateway_id_1) + describe_response["NatGateways"][0]["State"].should.equal("available") + describe_response["NatGateways"][0]["SubnetId"].should.equal(subnet_id_1) + describe_response["NatGateways"][0]["VpcId"].should.equal(vpc_id) + describe_response["NatGateways"][0]["NatGatewayAddresses"][0][ + "AllocationId" + ].should.equal(allocation_id_1) + + +@mock_ec2 +def test_describe_nat_gateway_filter_vpc_id(): + conn = boto3.client("ec2", "us-east-1") + vpc_1 = conn.create_vpc(CidrBlock="10.0.0.0/16") + vpc_id_1 = vpc_1["Vpc"]["VpcId"] + vpc_2 = conn.create_vpc(CidrBlock="10.1.0.0/16") + vpc_id_2 = vpc_2["Vpc"]["VpcId"] + subnet_1 = conn.create_subnet( + VpcId=vpc_id_1, CidrBlock="10.0.1.0/27", AvailabilityZone="us-east-1a" + ) + subnet_2 = conn.create_subnet( + VpcId=vpc_id_2, CidrBlock="10.1.1.0/27", AvailabilityZone="us-east-1a" + ) + allocation_id_1 = conn.allocate_address(Domain="vpc")["AllocationId"] + allocation_id_2 = conn.allocate_address(Domain="vpc")["AllocationId"] + subnet_id_1 = subnet_1["Subnet"]["SubnetId"] + subnet_id_2 = subnet_2["Subnet"]["SubnetId"] + + create_response_1 = conn.create_nat_gateway( + SubnetId=subnet_id_1, AllocationId=allocation_id_1 + ) + conn.create_nat_gateway(SubnetId=subnet_id_2, AllocationId=allocation_id_2) + nat_gateway_id_1 = create_response_1["NatGateway"]["NatGatewayId"] + + describe_response = conn.describe_nat_gateways() + describe_response["NatGateways"].should.have.length_of(2) + + describe_response = conn.describe_nat_gateways( + Filters=[{"Name": "vpc-id", "Values": [vpc_id_1]}] + ) + describe_response["NatGateways"].should.have.length_of(1) + describe_response["NatGateways"][0]["NatGatewayId"].should.equal(nat_gateway_id_1) + describe_response["NatGateways"][0]["State"].should.equal("available") + describe_response["NatGateways"][0]["SubnetId"].should.equal(subnet_id_1) + describe_response["NatGateways"][0]["VpcId"].should.equal(vpc_id_1) + describe_response["NatGateways"][0]["NatGatewayAddresses"][0][ + "AllocationId" + ].should.equal(allocation_id_1) diff --git a/tests/test_ec2/test_regions.py b/tests/test_ec2/test_regions.py index 551b739f2..3504a2b5a 100644 --- a/tests/test_ec2/test_regions.py +++ b/tests/test_ec2/test_regions.py @@ -3,13 +3,21 @@ import boto.ec2 import boto.ec2.autoscale import boto.ec2.elb import sure +from boto3 import Session + from moto import mock_ec2_deprecated, mock_autoscaling_deprecated, mock_elb_deprecated from moto.ec2 import ec2_backends def test_use_boto_regions(): - boto_regions = {r.name for r in boto.ec2.regions()} + boto_regions = set() + for region in Session().get_available_regions("ec2"): + boto_regions.add(region) + for region in Session().get_available_regions("ec2", partition_name="aws-us-gov"): + boto_regions.add(region) + for region in Session().get_available_regions("ec2", partition_name="aws-cn"): + boto_regions.add(region) moto_regions = set(ec2_backends) moto_regions.should.equal(boto_regions) diff --git a/tests/test_ec2/test_route_tables.py b/tests/test_ec2/test_route_tables.py index b82313bc8..dfb3292b6 100644 --- a/tests/test_ec2/test_route_tables.py +++ b/tests/test_ec2/test_route_tables.py @@ -581,3 +581,40 @@ def test_create_route_with_invalid_destination_cidr_block_parameter(): destination_cidr_block ) ) + + +@mock_ec2 +def test_describe_route_tables_with_nat_gateway(): + ec2 = boto3.client("ec2", region_name="us-west-1") + vpc_id = ec2.create_vpc(CidrBlock="192.168.0.0/23")["Vpc"]["VpcId"] + igw_id = ec2.create_internet_gateway()["InternetGateway"]["InternetGatewayId"] + ec2.attach_internet_gateway(VpcId=vpc_id, InternetGatewayId=igw_id) + az = ec2.describe_availability_zones()["AvailabilityZones"][0]["ZoneName"] + sn_id = ec2.create_subnet( + AvailabilityZone=az, CidrBlock="192.168.0.0/24", VpcId=vpc_id + )["Subnet"]["SubnetId"] + route_table_id = ec2.create_route_table(VpcId=vpc_id)["RouteTable"]["RouteTableId"] + ec2.associate_route_table(SubnetId=sn_id, RouteTableId=route_table_id) + alloc_id = ec2.allocate_address(Domain="vpc")["AllocationId"] + nat_gw_id = ec2.create_nat_gateway(SubnetId=sn_id, AllocationId=alloc_id)[ + "NatGateway" + ]["NatGatewayId"] + ec2.create_route( + DestinationCidrBlock="0.0.0.0/0", + NatGatewayId=nat_gw_id, + RouteTableId=route_table_id, + ) + + route_table = ec2.describe_route_tables( + Filters=[{"Name": "route-table-id", "Values": [route_table_id]}] + )["RouteTables"][0] + nat_gw_routes = [ + route + for route in route_table["Routes"] + if route["DestinationCidrBlock"] == "0.0.0.0/0" + ] + + nat_gw_routes.should.have.length_of(1) + nat_gw_routes[0]["DestinationCidrBlock"].should.equal("0.0.0.0/0") + nat_gw_routes[0]["NatGatewayId"].should.equal(nat_gw_id) + nat_gw_routes[0]["State"].should.equal("active") diff --git a/tests/test_ec2/test_spot_fleet.py b/tests/test_ec2/test_spot_fleet.py index 7b27764a1..87b2f6291 100644 --- a/tests/test_ec2/test_spot_fleet.py +++ b/tests/test_ec2/test_spot_fleet.py @@ -4,6 +4,7 @@ import boto3 import sure # noqa from moto import mock_ec2 +from moto.core import ACCOUNT_ID def get_subnet_id(conn): @@ -20,7 +21,7 @@ def spot_config(subnet_id, allocation_strategy="lowestPrice"): "ClientToken": "string", "SpotPrice": "0.12", "TargetCapacity": 6, - "IamFleetRole": "arn:aws:iam::123456789012:role/fleet", + "IamFleetRole": "arn:aws:iam::{}:role/fleet".format(ACCOUNT_ID), "LaunchSpecifications": [ { "ImageId": "ami-123", @@ -45,7 +46,9 @@ def spot_config(subnet_id, allocation_strategy="lowestPrice"): ], "Monitoring": {"Enabled": True}, "SubnetId": subnet_id, - "IamInstanceProfile": {"Arn": "arn:aws:iam::123456789012:role/fleet"}, + "IamInstanceProfile": { + "Arn": "arn:aws:iam::{}:role/fleet".format(ACCOUNT_ID) + }, "EbsOptimized": False, "WeightedCapacity": 2.0, "SpotPrice": "0.13", @@ -58,7 +61,9 @@ def spot_config(subnet_id, allocation_strategy="lowestPrice"): "InstanceType": "t2.large", "Monitoring": {"Enabled": True}, "SubnetId": subnet_id, - "IamInstanceProfile": {"Arn": "arn:aws:iam::123456789012:role/fleet"}, + "IamInstanceProfile": { + "Arn": "arn:aws:iam::{}:role/fleet".format(ACCOUNT_ID) + }, "EbsOptimized": False, "WeightedCapacity": 4.0, "SpotPrice": "10.00", @@ -90,7 +95,7 @@ def test_create_spot_fleet_with_lowest_price(): spot_fleet_config["SpotPrice"].should.equal("0.12") spot_fleet_config["TargetCapacity"].should.equal(6) spot_fleet_config["IamFleetRole"].should.equal( - "arn:aws:iam::123456789012:role/fleet" + "arn:aws:iam::{}:role/fleet".format(ACCOUNT_ID) ) spot_fleet_config["AllocationStrategy"].should.equal("lowestPrice") spot_fleet_config["FulfilledCapacity"].should.equal(6.0) @@ -101,7 +106,7 @@ def test_create_spot_fleet_with_lowest_price(): launch_spec["EbsOptimized"].should.equal(False) launch_spec["SecurityGroups"].should.equal([{"GroupId": "sg-123"}]) launch_spec["IamInstanceProfile"].should.equal( - {"Arn": "arn:aws:iam::123456789012:role/fleet"} + {"Arn": "arn:aws:iam::{}:role/fleet".format(ACCOUNT_ID)} ) launch_spec["ImageId"].should.equal("ami-123") launch_spec["InstanceType"].should.equal("t2.small") diff --git a/tests/test_ec2_instance_connect/test_ec2_instance_connect_boto3.py b/tests/test_ec2_instance_connect/test_ec2_instance_connect_boto3.py new file mode 100644 index 000000000..eb685d80a --- /dev/null +++ b/tests/test_ec2_instance_connect/test_ec2_instance_connect_boto3.py @@ -0,0 +1,23 @@ +import boto3 + +from moto import mock_ec2_instance_connect + +pubkey = """ssh-rsa +AAAAB3NzaC1yc2EAAAADAQABAAABAQDV5+voluw2zmzqpqCAqtsyoP01TQ8Ydx1eS1yD6wUsHcPqMIqpo57YxiC8XPwrdeKQ6GG6MC3bHsgXoPypGP0LyixbiuLTU31DnnqorcHt4bWs6rQa7dK2pCCflz2fhYRt5ZjqSNsAKivIbqkH66JozN0SySIka3kEV79GdB0BicioKeEJlCwM9vvxafyzjWf/z8E0lh4ni3vkLpIVJ0t5l+Qd9QMJrT6Is0SCQPVagTYZoi8+fWDoGsBa8vyRwDjEzBl28ZplKh9tSyDkRIYszWTpmK8qHiqjLYZBfAxXjGJbEYL1iig4ZxvbYzKEiKSBi1ZMW9iWjHfZDZuxXAmB +example +""" + + +@mock_ec2_instance_connect +def test_send_ssh_public_key(): + client = boto3.client("ec2-instance-connect", region_name="us-east-1") + fake_request_id = "example-2a47-4c91-9700-e37e85162cb6" + + response = client.send_ssh_public_key( + InstanceId="i-abcdefg12345", + InstanceOSUser="ec2-user", + SSHPublicKey=pubkey, + AvailabilityZone="us-east-1a", + ) + + assert response["RequestId"] == fake_request_id diff --git a/tests/test_elb/test_elb.py b/tests/test_elb/test_elb.py index d7a7b88cb..1583ea544 100644 --- a/tests/test_elb/test_elb.py +++ b/tests/test_elb/test_elb.py @@ -15,6 +15,7 @@ from nose.tools import assert_raises import sure # noqa from moto import mock_elb, mock_ec2, mock_elb_deprecated, mock_ec2_deprecated +from moto.core import ACCOUNT_ID @mock_elb_deprecated @@ -76,7 +77,12 @@ def test_create_load_balancer_with_certificate(): zones = ["us-east-1a"] ports = [ - (443, 8443, "https", "arn:aws:iam:123456789012:server-certificate/test-cert") + ( + 443, + 8443, + "https", + "arn:aws:iam:{}:server-certificate/test-cert".format(ACCOUNT_ID), + ) ] conn.create_load_balancer("my-lb", zones, ports) @@ -90,7 +96,7 @@ def test_create_load_balancer_with_certificate(): listener.instance_port.should.equal(8443) listener.protocol.should.equal("HTTPS") listener.ssl_certificate_id.should.equal( - "arn:aws:iam:123456789012:server-certificate/test-cert" + "arn:aws:iam:{}:server-certificate/test-cert".format(ACCOUNT_ID) ) diff --git a/tests/test_elbv2/test_elbv2.py b/tests/test_elbv2/test_elbv2.py index 593ced43b..eb5df14c3 100644 --- a/tests/test_elbv2/test_elbv2.py +++ b/tests/test_elbv2/test_elbv2.py @@ -10,6 +10,7 @@ import sure # noqa from moto import mock_elbv2, mock_ec2, mock_acm, mock_cloudformation from moto.elbv2 import elbv2_backends +from moto.core import ACCOUNT_ID @mock_elbv2 @@ -346,7 +347,11 @@ def test_create_target_group_and_listeners(): Protocol="HTTPS", Port=443, Certificates=[ - {"CertificateArn": "arn:aws:iam:123456789012:server-certificate/test-cert"} + { + "CertificateArn": "arn:aws:iam:{}:server-certificate/test-cert".format( + ACCOUNT_ID + ) + } ], DefaultActions=[ {"Type": "forward", "TargetGroupArn": target_group.get("TargetGroupArn")} @@ -356,7 +361,13 @@ def test_create_target_group_and_listeners(): listener.get("Port").should.equal(443) listener.get("Protocol").should.equal("HTTPS") listener.get("Certificates").should.equal( - [{"CertificateArn": "arn:aws:iam:123456789012:server-certificate/test-cert"}] + [ + { + "CertificateArn": "arn:aws:iam:{}:server-certificate/test-cert".format( + ACCOUNT_ID + ) + } + ] ) listener.get("DefaultActions").should.equal( [{"TargetGroupArn": target_group.get("TargetGroupArn"), "Type": "forward"}] @@ -1902,7 +1913,9 @@ def test_cognito_action_listener_rule(): action = { "Type": "authenticate-cognito", "AuthenticateCognitoConfig": { - "UserPoolArn": "arn:aws:cognito-idp:us-east-1:123456789012:userpool/us-east-1_ABCD1234", + "UserPoolArn": "arn:aws:cognito-idp:us-east-1:{}:userpool/us-east-1_ABCD1234".format( + ACCOUNT_ID + ), "UserPoolClientId": "abcd1234abcd", "UserPoolDomain": "testpool", }, @@ -1977,7 +1990,9 @@ def test_cognito_action_listener_rule_cloudformation(): { "Type": "authenticate-cognito", "AuthenticateCognitoConfig": { - "UserPoolArn": "arn:aws:cognito-idp:us-east-1:123456789012:userpool/us-east-1_ABCD1234", + "UserPoolArn": "arn:aws:cognito-idp:us-east-1:{}:userpool/us-east-1_ABCD1234".format( + ACCOUNT_ID + ), "UserPoolClientId": "abcd1234abcd", "UserPoolDomain": "testpool", }, @@ -2006,7 +2021,9 @@ def test_cognito_action_listener_rule_cloudformation(): { "Type": "authenticate-cognito", "AuthenticateCognitoConfig": { - "UserPoolArn": "arn:aws:cognito-idp:us-east-1:123456789012:userpool/us-east-1_ABCD1234", + "UserPoolArn": "arn:aws:cognito-idp:us-east-1:{}:userpool/us-east-1_ABCD1234".format( + ACCOUNT_ID + ), "UserPoolClientId": "abcd1234abcd", "UserPoolDomain": "testpool", }, diff --git a/tests/test_events/test_events.py b/tests/test_events/test_events.py index 5f81e2cf6..14d872806 100644 --- a/tests/test_events/test_events.py +++ b/tests/test_events/test_events.py @@ -6,6 +6,7 @@ import sure # noqa from moto.events import mock_events from botocore.exceptions import ClientError from nose.tools import assert_raises +from moto.core import ACCOUNT_ID RULES = [ {"Name": "test1", "ScheduleExpression": "rate(5 minutes)"}, @@ -276,7 +277,7 @@ def test_create_event_bus(): response = client.create_event_bus(Name="test-bus") response["EventBusArn"].should.equal( - "arn:aws:events:us-east-1:123456789012:event-bus/test-bus" + "arn:aws:events:us-east-1:{}:event-bus/test-bus".format(ACCOUNT_ID) ) @@ -314,7 +315,7 @@ def test_describe_event_bus(): response["Name"].should.equal("default") response["Arn"].should.equal( - "arn:aws:events:us-east-1:123456789012:event-bus/default" + "arn:aws:events:us-east-1:{}:event-bus/default".format(ACCOUNT_ID) ) response.should_not.have.key("Policy") @@ -330,7 +331,7 @@ def test_describe_event_bus(): response["Name"].should.equal("test-bus") response["Arn"].should.equal( - "arn:aws:events:us-east-1:123456789012:event-bus/test-bus" + "arn:aws:events:us-east-1:{}:event-bus/test-bus".format(ACCOUNT_ID) ) json.loads(response["Policy"]).should.equal( { @@ -341,7 +342,9 @@ def test_describe_event_bus(): "Effect": "Allow", "Principal": {"AWS": "arn:aws:iam::111111111111:root"}, "Action": "events:PutEvents", - "Resource": "arn:aws:events:us-east-1:123456789012:event-bus/test-bus", + "Resource": "arn:aws:events:us-east-1:{}:event-bus/test-bus".format( + ACCOUNT_ID + ), } ], } @@ -372,23 +375,33 @@ def test_list_event_buses(): [ { "Name": "default", - "Arn": "arn:aws:events:us-east-1:123456789012:event-bus/default", + "Arn": "arn:aws:events:us-east-1:{}:event-bus/default".format( + ACCOUNT_ID + ), }, { "Name": "other-bus-1", - "Arn": "arn:aws:events:us-east-1:123456789012:event-bus/other-bus-1", + "Arn": "arn:aws:events:us-east-1:{}:event-bus/other-bus-1".format( + ACCOUNT_ID + ), }, { "Name": "other-bus-2", - "Arn": "arn:aws:events:us-east-1:123456789012:event-bus/other-bus-2", + "Arn": "arn:aws:events:us-east-1:{}:event-bus/other-bus-2".format( + ACCOUNT_ID + ), }, { "Name": "test-bus-1", - "Arn": "arn:aws:events:us-east-1:123456789012:event-bus/test-bus-1", + "Arn": "arn:aws:events:us-east-1:{}:event-bus/test-bus-1".format( + ACCOUNT_ID + ), }, { "Name": "test-bus-2", - "Arn": "arn:aws:events:us-east-1:123456789012:event-bus/test-bus-2", + "Arn": "arn:aws:events:us-east-1:{}:event-bus/test-bus-2".format( + ACCOUNT_ID + ), }, ] ) @@ -400,11 +413,15 @@ def test_list_event_buses(): [ { "Name": "other-bus-1", - "Arn": "arn:aws:events:us-east-1:123456789012:event-bus/other-bus-1", + "Arn": "arn:aws:events:us-east-1:{}:event-bus/other-bus-1".format( + ACCOUNT_ID + ), }, { "Name": "other-bus-2", - "Arn": "arn:aws:events:us-east-1:123456789012:event-bus/other-bus-2", + "Arn": "arn:aws:events:us-east-1:{}:event-bus/other-bus-2".format( + ACCOUNT_ID + ), }, ] ) @@ -426,7 +443,9 @@ def test_delete_event_bus(): [ { "Name": "default", - "Arn": "arn:aws:events:us-east-1:123456789012:event-bus/default", + "Arn": "arn:aws:events:us-east-1:{}:event-bus/default".format( + ACCOUNT_ID + ), } ] ) diff --git a/tests/test_glacier/test_glacier_jobs.py b/tests/test_glacier/test_glacier_jobs.py index 11077d7f2..cba2c1a27 100644 --- a/tests/test_glacier/test_glacier_jobs.py +++ b/tests/test_glacier/test_glacier_jobs.py @@ -44,7 +44,7 @@ def test_describe_job(): joboutput.should.have.key("Tier").which.should.equal("Standard") joboutput.should.have.key("StatusCode").which.should.equal("InProgress") joboutput.should.have.key("VaultARN").which.should.equal( - "arn:aws:glacier:RegionInfo:us-west-2:012345678901:vaults/my_vault" + "arn:aws:glacier:us-west-2:012345678901:vaults/my_vault" ) diff --git a/tests/test_iam/test_iam.py b/tests/test_iam/test_iam.py index 5098de413..9a2c1f0dd 100644 --- a/tests/test_iam/test_iam.py +++ b/tests/test_iam/test_iam.py @@ -13,6 +13,7 @@ from dateutil.tz import tzutc from moto import mock_iam, mock_iam_deprecated from moto.iam.models import aws_managed_policies +from moto.core import ACCOUNT_ID from nose.tools import assert_raises, assert_equals from nose.tools import raises @@ -83,7 +84,9 @@ def test_get_all_server_certs(): certs.should.have.length_of(1) cert1 = certs[0] cert1.server_certificate_name.should.equal("certname") - cert1.arn.should.equal("arn:aws:iam::123456789012:server-certificate/certname") + cert1.arn.should.equal( + "arn:aws:iam::{}:server-certificate/certname".format(ACCOUNT_ID) + ) @mock_iam_deprecated() @@ -101,7 +104,9 @@ def test_get_server_cert(): conn.upload_server_cert("certname", "certbody", "privatekey") cert = conn.get_server_certificate("certname") cert.server_certificate_name.should.equal("certname") - cert.arn.should.equal("arn:aws:iam::123456789012:server-certificate/certname") + cert.arn.should.equal( + "arn:aws:iam::{}:server-certificate/certname".format(ACCOUNT_ID) + ) @mock_iam_deprecated() @@ -111,7 +116,9 @@ def test_upload_server_cert(): conn.upload_server_cert("certname", "certbody", "privatekey") cert = conn.get_server_certificate("certname") cert.server_certificate_name.should.equal("certname") - cert.arn.should.equal("arn:aws:iam::123456789012:server-certificate/certname") + cert.arn.should.equal( + "arn:aws:iam::{}:server-certificate/certname".format(ACCOUNT_ID) + ) @mock_iam_deprecated() @@ -405,7 +412,7 @@ def test_create_policy(): PolicyName="TestCreatePolicy", PolicyDocument=MOCK_POLICY ) response["Policy"]["Arn"].should.equal( - "arn:aws:iam::123456789012:policy/TestCreatePolicy" + "arn:aws:iam::{}:policy/TestCreatePolicy".format(ACCOUNT_ID) ) @@ -442,12 +449,14 @@ def test_create_policy_versions(): conn = boto3.client("iam", region_name="us-east-1") with assert_raises(ClientError): conn.create_policy_version( - PolicyArn="arn:aws:iam::123456789012:policy/TestCreatePolicyVersion", + PolicyArn="arn:aws:iam::{}:policy/TestCreatePolicyVersion".format( + ACCOUNT_ID + ), PolicyDocument='{"some":"policy"}', ) conn.create_policy(PolicyName="TestCreatePolicyVersion", PolicyDocument=MOCK_POLICY) version = conn.create_policy_version( - PolicyArn="arn:aws:iam::123456789012:policy/TestCreatePolicyVersion", + PolicyArn="arn:aws:iam::{}:policy/TestCreatePolicyVersion".format(ACCOUNT_ID), PolicyDocument=MOCK_POLICY, SetAsDefault=True, ) @@ -455,11 +464,11 @@ def test_create_policy_versions(): version.get("PolicyVersion").get("VersionId").should.equal("v2") version.get("PolicyVersion").get("IsDefaultVersion").should.be.ok conn.delete_policy_version( - PolicyArn="arn:aws:iam::123456789012:policy/TestCreatePolicyVersion", + PolicyArn="arn:aws:iam::{}:policy/TestCreatePolicyVersion".format(ACCOUNT_ID), VersionId="v1", ) version = conn.create_policy_version( - PolicyArn="arn:aws:iam::123456789012:policy/TestCreatePolicyVersion", + PolicyArn="arn:aws:iam::{}:policy/TestCreatePolicyVersion".format(ACCOUNT_ID), PolicyDocument=MOCK_POLICY, ) version.get("PolicyVersion").get("VersionId").should.equal("v3") @@ -474,12 +483,16 @@ def test_create_many_policy_versions(): ) for _ in range(0, 4): conn.create_policy_version( - PolicyArn="arn:aws:iam::123456789012:policy/TestCreateManyPolicyVersions", + PolicyArn="arn:aws:iam::{}:policy/TestCreateManyPolicyVersions".format( + ACCOUNT_ID + ), PolicyDocument=MOCK_POLICY, ) with assert_raises(ClientError): conn.create_policy_version( - PolicyArn="arn:aws:iam::123456789012:policy/TestCreateManyPolicyVersions", + PolicyArn="arn:aws:iam::{}:policy/TestCreateManyPolicyVersions".format( + ACCOUNT_ID + ), PolicyDocument=MOCK_POLICY, ) @@ -491,17 +504,23 @@ def test_set_default_policy_version(): PolicyName="TestSetDefaultPolicyVersion", PolicyDocument=MOCK_POLICY ) conn.create_policy_version( - PolicyArn="arn:aws:iam::123456789012:policy/TestSetDefaultPolicyVersion", + PolicyArn="arn:aws:iam::{}:policy/TestSetDefaultPolicyVersion".format( + ACCOUNT_ID + ), PolicyDocument=MOCK_POLICY_2, SetAsDefault=True, ) conn.create_policy_version( - PolicyArn="arn:aws:iam::123456789012:policy/TestSetDefaultPolicyVersion", + PolicyArn="arn:aws:iam::{}:policy/TestSetDefaultPolicyVersion".format( + ACCOUNT_ID + ), PolicyDocument=MOCK_POLICY_3, SetAsDefault=True, ) versions = conn.list_policy_versions( - PolicyArn="arn:aws:iam::123456789012:policy/TestSetDefaultPolicyVersion" + PolicyArn="arn:aws:iam::{}:policy/TestSetDefaultPolicyVersion".format( + ACCOUNT_ID + ) ) versions.get("Versions")[0].get("Document").should.equal(json.loads(MOCK_POLICY)) versions.get("Versions")[0].get("IsDefaultVersion").shouldnt.be.ok @@ -517,9 +536,11 @@ def test_get_policy(): response = conn.create_policy( PolicyName="TestGetPolicy", PolicyDocument=MOCK_POLICY ) - policy = conn.get_policy(PolicyArn="arn:aws:iam::123456789012:policy/TestGetPolicy") + policy = conn.get_policy( + PolicyArn="arn:aws:iam::{}:policy/TestGetPolicy".format(ACCOUNT_ID) + ) policy["Policy"]["Arn"].should.equal( - "arn:aws:iam::123456789012:policy/TestGetPolicy" + "arn:aws:iam::{}:policy/TestGetPolicy".format(ACCOUNT_ID) ) @@ -542,16 +563,16 @@ def test_get_policy_version(): conn = boto3.client("iam", region_name="us-east-1") conn.create_policy(PolicyName="TestGetPolicyVersion", PolicyDocument=MOCK_POLICY) version = conn.create_policy_version( - PolicyArn="arn:aws:iam::123456789012:policy/TestGetPolicyVersion", + PolicyArn="arn:aws:iam::{}:policy/TestGetPolicyVersion".format(ACCOUNT_ID), PolicyDocument=MOCK_POLICY, ) with assert_raises(ClientError): conn.get_policy_version( - PolicyArn="arn:aws:iam::123456789012:policy/TestGetPolicyVersion", + PolicyArn="arn:aws:iam::{}:policy/TestGetPolicyVersion".format(ACCOUNT_ID), VersionId="v2-does-not-exist", ) retrieved = conn.get_policy_version( - PolicyArn="arn:aws:iam::123456789012:policy/TestGetPolicyVersion", + PolicyArn="arn:aws:iam::{}:policy/TestGetPolicyVersion".format(ACCOUNT_ID), VersionId=version.get("PolicyVersion").get("VersionId"), ) retrieved.get("PolicyVersion").get("Document").should.equal(json.loads(MOCK_POLICY)) @@ -601,25 +622,25 @@ def test_list_policy_versions(): conn = boto3.client("iam", region_name="us-east-1") with assert_raises(ClientError): versions = conn.list_policy_versions( - PolicyArn="arn:aws:iam::123456789012:policy/TestListPolicyVersions" + PolicyArn="arn:aws:iam::{}:policy/TestListPolicyVersions".format(ACCOUNT_ID) ) conn.create_policy(PolicyName="TestListPolicyVersions", PolicyDocument=MOCK_POLICY) versions = conn.list_policy_versions( - PolicyArn="arn:aws:iam::123456789012:policy/TestListPolicyVersions" + PolicyArn="arn:aws:iam::{}:policy/TestListPolicyVersions".format(ACCOUNT_ID) ) versions.get("Versions")[0].get("VersionId").should.equal("v1") versions.get("Versions")[0].get("IsDefaultVersion").should.be.ok conn.create_policy_version( - PolicyArn="arn:aws:iam::123456789012:policy/TestListPolicyVersions", + PolicyArn="arn:aws:iam::{}:policy/TestListPolicyVersions".format(ACCOUNT_ID), PolicyDocument=MOCK_POLICY_2, ) conn.create_policy_version( - PolicyArn="arn:aws:iam::123456789012:policy/TestListPolicyVersions", + PolicyArn="arn:aws:iam::{}:policy/TestListPolicyVersions".format(ACCOUNT_ID), PolicyDocument=MOCK_POLICY_3, ) versions = conn.list_policy_versions( - PolicyArn="arn:aws:iam::123456789012:policy/TestListPolicyVersions" + PolicyArn="arn:aws:iam::{}:policy/TestListPolicyVersions".format(ACCOUNT_ID) ) versions.get("Versions")[1].get("Document").should.equal(json.loads(MOCK_POLICY_2)) versions.get("Versions")[1].get("IsDefaultVersion").shouldnt.be.ok @@ -632,20 +653,22 @@ def test_delete_policy_version(): conn = boto3.client("iam", region_name="us-east-1") conn.create_policy(PolicyName="TestDeletePolicyVersion", PolicyDocument=MOCK_POLICY) conn.create_policy_version( - PolicyArn="arn:aws:iam::123456789012:policy/TestDeletePolicyVersion", + PolicyArn="arn:aws:iam::{}:policy/TestDeletePolicyVersion".format(ACCOUNT_ID), PolicyDocument=MOCK_POLICY, ) with assert_raises(ClientError): conn.delete_policy_version( - PolicyArn="arn:aws:iam::123456789012:policy/TestDeletePolicyVersion", + PolicyArn="arn:aws:iam::{}:policy/TestDeletePolicyVersion".format( + ACCOUNT_ID + ), VersionId="v2-nope-this-does-not-exist", ) conn.delete_policy_version( - PolicyArn="arn:aws:iam::123456789012:policy/TestDeletePolicyVersion", + PolicyArn="arn:aws:iam::{}:policy/TestDeletePolicyVersion".format(ACCOUNT_ID), VersionId="v2", ) versions = conn.list_policy_versions( - PolicyArn="arn:aws:iam::123456789012:policy/TestDeletePolicyVersion" + PolicyArn="arn:aws:iam::{}:policy/TestDeletePolicyVersion".format(ACCOUNT_ID) ) len(versions.get("Versions")).should.equal(1) @@ -655,12 +678,14 @@ def test_delete_default_policy_version(): conn = boto3.client("iam", region_name="us-east-1") conn.create_policy(PolicyName="TestDeletePolicyVersion", PolicyDocument=MOCK_POLICY) conn.create_policy_version( - PolicyArn="arn:aws:iam::123456789012:policy/TestDeletePolicyVersion", + PolicyArn="arn:aws:iam::{}:policy/TestDeletePolicyVersion".format(ACCOUNT_ID), PolicyDocument=MOCK_POLICY_2, ) with assert_raises(ClientError): conn.delete_policy_version( - PolicyArn="arn:aws:iam::123456789012:policy/TestDeletePolicyVersion", + PolicyArn="arn:aws:iam::{}:policy/TestDeletePolicyVersion".format( + ACCOUNT_ID + ), VersionId="v1", ) @@ -713,7 +738,7 @@ def test_list_users(): user = response["Users"][0] user["UserName"].should.equal("my-user") user["Path"].should.equal("/") - user["Arn"].should.equal("arn:aws:iam::123456789012:user/my-user") + user["Arn"].should.equal("arn:aws:iam::{}:user/my-user".format(ACCOUNT_ID)) @mock_iam() @@ -839,7 +864,9 @@ def test_create_virtual_mfa_device(): response = client.create_virtual_mfa_device(VirtualMFADeviceName="test-device") device = response["VirtualMFADevice"] - device["SerialNumber"].should.equal("arn:aws:iam::123456789012:mfa/test-device") + device["SerialNumber"].should.equal( + "arn:aws:iam::{}:mfa/test-device".format(ACCOUNT_ID) + ) device["Base32StringSeed"].decode("ascii").should.match("[A-Z234567]") device["QRCodePNG"].should_not.be.empty @@ -848,7 +875,9 @@ def test_create_virtual_mfa_device(): ) device = response["VirtualMFADevice"] - device["SerialNumber"].should.equal("arn:aws:iam::123456789012:mfa/test-device-2") + device["SerialNumber"].should.equal( + "arn:aws:iam::{}:mfa/test-device-2".format(ACCOUNT_ID) + ) device["Base32StringSeed"].decode("ascii").should.match("[A-Z234567]") device["QRCodePNG"].should_not.be.empty @@ -858,7 +887,7 @@ def test_create_virtual_mfa_device(): device = response["VirtualMFADevice"] device["SerialNumber"].should.equal( - "arn:aws:iam::123456789012:mfa/test/test-device" + "arn:aws:iam::{}:mfa/test/test-device".format(ACCOUNT_ID) ) device["Base32StringSeed"].decode("ascii").should.match("[A-Z234567]") device["QRCodePNG"].should_not.be.empty @@ -920,7 +949,7 @@ def test_delete_virtual_mfa_device(): def test_delete_virtual_mfa_device_errors(): client = boto3.client("iam", region_name="us-east-1") - serial_number = "arn:aws:iam::123456789012:mfa/not-existing" + serial_number = "arn:aws:iam::{}:mfa/not-existing".format(ACCOUNT_ID) client.delete_virtual_mfa_device.when.called_with( SerialNumber=serial_number ).should.throw( @@ -1009,7 +1038,9 @@ def test_enable_virtual_mfa_device(): device["User"]["Path"].should.equal("/") device["User"]["UserName"].should.equal("test-user") device["User"]["UserId"].should_not.be.empty - device["User"]["Arn"].should.equal("arn:aws:iam::123456789012:user/test-user") + device["User"]["Arn"].should.equal( + "arn:aws:iam::{}:user/test-user".format(ACCOUNT_ID) + ) device["User"]["CreateDate"].should.be.a(datetime) device["EnableDate"].should.be.a(datetime) response["IsTruncated"].should_not.be.ok @@ -1444,7 +1475,7 @@ def test_get_account_authorization_details(): ) conn = boto3.client("iam", region_name="us-east-1") - boundary = "arn:aws:iam::123456789012:policy/boundary" + boundary = "arn:aws:iam::{}:policy/boundary".format(ACCOUNT_ID) conn.create_role( RoleName="my-role", AssumeRolePolicyDocument="some policy", @@ -1470,10 +1501,12 @@ def test_get_account_authorization_details(): ) conn.attach_user_policy( - UserName="testUser", PolicyArn="arn:aws:iam::123456789012:policy/testPolicy" + UserName="testUser", + PolicyArn="arn:aws:iam::{}:policy/testPolicy".format(ACCOUNT_ID), ) conn.attach_group_policy( - GroupName="testGroup", PolicyArn="arn:aws:iam::123456789012:policy/testPolicy" + GroupName="testGroup", + PolicyArn="arn:aws:iam::{}:policy/testPolicy".format(ACCOUNT_ID), ) conn.add_user_to_group(UserName="testUser", GroupName="testGroup") @@ -1492,7 +1525,8 @@ def test_get_account_authorization_details(): RoleName="my-role", PolicyName="test-policy", PolicyDocument=test_policy ) conn.attach_role_policy( - RoleName="my-role", PolicyArn="arn:aws:iam::123456789012:policy/testPolicy" + RoleName="my-role", + PolicyArn="arn:aws:iam::{}:policy/testPolicy".format(ACCOUNT_ID), ) result = conn.get_account_authorization_details(Filter=["Role"]) @@ -1509,7 +1543,7 @@ def test_get_account_authorization_details(): "PermissionsBoundary" ] == { "PermissionsBoundaryType": "PermissionsBoundaryPolicy", - "PermissionsBoundaryArn": "arn:aws:iam::123456789012:policy/boundary", + "PermissionsBoundaryArn": "arn:aws:iam::{}:policy/boundary".format(ACCOUNT_ID), } assert len(result["RoleDetailList"][0]["Tags"]) == 2 assert len(result["RoleDetailList"][0]["RolePolicyList"]) == 1 @@ -1518,10 +1552,9 @@ def test_get_account_authorization_details(): result["RoleDetailList"][0]["AttachedManagedPolicies"][0]["PolicyName"] == "testPolicy" ) - assert ( - result["RoleDetailList"][0]["AttachedManagedPolicies"][0]["PolicyArn"] - == "arn:aws:iam::123456789012:policy/testPolicy" - ) + assert result["RoleDetailList"][0]["AttachedManagedPolicies"][0][ + "PolicyArn" + ] == "arn:aws:iam::{}:policy/testPolicy".format(ACCOUNT_ID) result = conn.get_account_authorization_details(Filter=["User"]) assert len(result["RoleDetailList"]) == 0 @@ -1534,10 +1567,9 @@ def test_get_account_authorization_details(): result["UserDetailList"][0]["AttachedManagedPolicies"][0]["PolicyName"] == "testPolicy" ) - assert ( - result["UserDetailList"][0]["AttachedManagedPolicies"][0]["PolicyArn"] - == "arn:aws:iam::123456789012:policy/testPolicy" - ) + assert result["UserDetailList"][0]["AttachedManagedPolicies"][0][ + "PolicyArn" + ] == "arn:aws:iam::{}:policy/testPolicy".format(ACCOUNT_ID) result = conn.get_account_authorization_details(Filter=["Group"]) assert len(result["RoleDetailList"]) == 0 @@ -1550,10 +1582,9 @@ def test_get_account_authorization_details(): result["GroupDetailList"][0]["AttachedManagedPolicies"][0]["PolicyName"] == "testPolicy" ) - assert ( - result["GroupDetailList"][0]["AttachedManagedPolicies"][0]["PolicyArn"] - == "arn:aws:iam::123456789012:policy/testPolicy" - ) + assert result["GroupDetailList"][0]["AttachedManagedPolicies"][0][ + "PolicyArn" + ] == "arn:aws:iam::{}:policy/testPolicy".format(ACCOUNT_ID) result = conn.get_account_authorization_details(Filter=["LocalManagedPolicy"]) assert len(result["RoleDetailList"]) == 0 @@ -1650,7 +1681,7 @@ def test_create_saml_provider(): Name="TestSAMLProvider", SAMLMetadataDocument="a" * 1024 ) response["SAMLProviderArn"].should.equal( - "arn:aws:iam::123456789012:saml-provider/TestSAMLProvider" + "arn:aws:iam::{}:saml-provider/TestSAMLProvider".format(ACCOUNT_ID) ) @@ -1672,7 +1703,7 @@ def test_list_saml_providers(): conn.create_saml_provider(Name="TestSAMLProvider", SAMLMetadataDocument="a" * 1024) response = conn.list_saml_providers() response["SAMLProviderList"][0]["Arn"].should.equal( - "arn:aws:iam::123456789012:saml-provider/TestSAMLProvider" + "arn:aws:iam::{}:saml-provider/TestSAMLProvider".format(ACCOUNT_ID) ) @@ -1702,6 +1733,19 @@ def test_delete_saml_provider(): assert not resp["Certificates"] +@mock_iam() +def test_create_role_defaults(): + """Tests default values""" + conn = boto3.client("iam", region_name="us-east-1") + conn.create_role(RoleName="my-role", AssumeRolePolicyDocument="{}") + + # Get role: + role = conn.get_role(RoleName="my-role")["Role"] + + assert role["MaxSessionDuration"] == 3600 + assert role.get("Description") is None + + @mock_iam() def test_create_role_with_tags(): """Tests both the tag_role and get_role_tags capability""" @@ -2044,6 +2088,28 @@ def test_update_role(): assert len(response.keys()) == 1 +@mock_iam() +def test_update_role_defaults(): + conn = boto3.client("iam", region_name="us-east-1") + + with assert_raises(ClientError): + conn.delete_role(RoleName="my-role") + + conn.create_role( + RoleName="my-role", + AssumeRolePolicyDocument="some policy", + Description="test", + Path="/my-path/", + ) + response = conn.update_role(RoleName="my-role") + assert len(response.keys()) == 1 + + role = conn.get_role(RoleName="my-role")["Role"] + + assert role["MaxSessionDuration"] == 3600 + assert role.get("Description") is None + + @mock_iam() def test_list_entities_for_policy(): test_policy = json.dumps( @@ -2077,10 +2143,12 @@ def test_list_entities_for_policy(): ) conn.attach_user_policy( - UserName="testUser", PolicyArn="arn:aws:iam::123456789012:policy/testPolicy" + UserName="testUser", + PolicyArn="arn:aws:iam::{}:policy/testPolicy".format(ACCOUNT_ID), ) conn.attach_group_policy( - GroupName="testGroup", PolicyArn="arn:aws:iam::123456789012:policy/testPolicy" + GroupName="testGroup", + PolicyArn="arn:aws:iam::{}:policy/testPolicy".format(ACCOUNT_ID), ) conn.add_user_to_group(UserName="testUser", GroupName="testGroup") @@ -2099,26 +2167,30 @@ def test_list_entities_for_policy(): RoleName="my-role", PolicyName="test-policy", PolicyDocument=test_policy ) conn.attach_role_policy( - RoleName="my-role", PolicyArn="arn:aws:iam::123456789012:policy/testPolicy" + RoleName="my-role", + PolicyArn="arn:aws:iam::{}:policy/testPolicy".format(ACCOUNT_ID), ) response = conn.list_entities_for_policy( - PolicyArn="arn:aws:iam::123456789012:policy/testPolicy", EntityFilter="Role" + PolicyArn="arn:aws:iam::{}:policy/testPolicy".format(ACCOUNT_ID), + EntityFilter="Role", ) assert response["PolicyRoles"] == [{"RoleName": "my-role"}] response = conn.list_entities_for_policy( - PolicyArn="arn:aws:iam::123456789012:policy/testPolicy", EntityFilter="User" + PolicyArn="arn:aws:iam::{}:policy/testPolicy".format(ACCOUNT_ID), + EntityFilter="User", ) assert response["PolicyUsers"] == [{"UserName": "testUser"}] response = conn.list_entities_for_policy( - PolicyArn="arn:aws:iam::123456789012:policy/testPolicy", EntityFilter="Group" + PolicyArn="arn:aws:iam::{}:policy/testPolicy".format(ACCOUNT_ID), + EntityFilter="Group", ) assert response["PolicyGroups"] == [{"GroupName": "testGroup"}] response = conn.list_entities_for_policy( - PolicyArn="arn:aws:iam::123456789012:policy/testPolicy", + PolicyArn="arn:aws:iam::{}:policy/testPolicy".format(ACCOUNT_ID), EntityFilter="LocalManagedPolicy", ) assert response["PolicyGroups"] == [{"GroupName": "testGroup"}] @@ -2132,7 +2204,9 @@ def test_create_role_no_path(): resp = conn.create_role( RoleName="my-role", AssumeRolePolicyDocument="some policy", Description="test" ) - resp.get("Role").get("Arn").should.equal("arn:aws:iam::123456789012:role/my-role") + resp.get("Role").get("Arn").should.equal( + "arn:aws:iam::{}:role/my-role".format(ACCOUNT_ID) + ) resp.get("Role").should_not.have.key("PermissionsBoundary") resp.get("Role").get("Description").should.equal("test") @@ -2140,7 +2214,7 @@ def test_create_role_no_path(): @mock_iam() def test_create_role_with_permissions_boundary(): conn = boto3.client("iam", region_name="us-east-1") - boundary = "arn:aws:iam::123456789012:policy/boundary" + boundary = "arn:aws:iam::{}:policy/boundary".format(ACCOUNT_ID) resp = conn.create_role( RoleName="my-role", AssumeRolePolicyDocument="some policy", @@ -2212,7 +2286,7 @@ def test_create_open_id_connect_provider(): ) response["OpenIDConnectProviderArn"].should.equal( - "arn:aws:iam::123456789012:oidc-provider/example.com" + "arn:aws:iam::{}:oidc-provider/example.com".format(ACCOUNT_ID) ) response = client.create_open_id_connect_provider( @@ -2220,7 +2294,7 @@ def test_create_open_id_connect_provider(): ) response["OpenIDConnectProviderArn"].should.equal( - "arn:aws:iam::123456789012:oidc-provider/example.org" + "arn:aws:iam::{}:oidc-provider/example.org".format(ACCOUNT_ID) ) response = client.create_open_id_connect_provider( @@ -2228,7 +2302,7 @@ def test_create_open_id_connect_provider(): ) response["OpenIDConnectProviderArn"].should.equal( - "arn:aws:iam::123456789012:oidc-provider/example.org/oidc" + "arn:aws:iam::{}:oidc-provider/example.org/oidc".format(ACCOUNT_ID) ) response = client.create_open_id_connect_provider( @@ -2236,7 +2310,7 @@ def test_create_open_id_connect_provider(): ) response["OpenIDConnectProviderArn"].should.equal( - "arn:aws:iam::123456789012:oidc-provider/example.org/oidc-query" + "arn:aws:iam::{}:oidc-provider/example.org/oidc-query".format(ACCOUNT_ID) ) @@ -2450,7 +2524,7 @@ def test_get_account_password_policy_errors(): client.get_account_password_policy.when.called_with().should.throw( ClientError, - "The Password Policy with domain name 123456789012 cannot be found.", + "The Password Policy with domain name {} cannot be found.".format(ACCOUNT_ID), ) @@ -2467,7 +2541,7 @@ def test_delete_account_password_policy(): client.get_account_password_policy.when.called_with().should.throw( ClientError, - "The Password Policy with domain name 123456789012 cannot be found.", + "The Password Policy with domain name {} cannot be found.".format(ACCOUNT_ID), ) @@ -2596,3 +2670,33 @@ def test_get_account_summary(): "GroupsQuota": 300, } ) + + +@mock_iam() +def test_list_user_tags(): + """Tests both setting a tags on a user in create_user and list_user_tags""" + conn = boto3.client("iam", region_name="us-east-1") + conn.create_user(UserName="kenny-bania") + conn.create_user( + UserName="jackie-chiles", Tags=[{"Key": "Sue-Allen", "Value": "Oh-Henry"}] + ) + conn.create_user( + UserName="cosmo", + Tags=[ + {"Key": "Stan", "Value": "The Caddy"}, + {"Key": "like-a", "Value": "glove"}, + ], + ) + response = conn.list_user_tags(UserName="kenny-bania") + response["Tags"].should.equal([]) + response["IsTruncated"].should_not.be.ok + + response = conn.list_user_tags(UserName="jackie-chiles") + response["Tags"].should.equal([{"Key": "Sue-Allen", "Value": "Oh-Henry"}]) + response["IsTruncated"].should_not.be.ok + + response = conn.list_user_tags(UserName="cosmo") + response["Tags"].should.equal( + [{"Key": "Stan", "Value": "The Caddy"}, {"Key": "like-a", "Value": "glove"}] + ) + response["IsTruncated"].should_not.be.ok diff --git a/tests/test_iam/test_iam_groups.py b/tests/test_iam/test_iam_groups.py index 7b73e89ea..64d838e2b 100644 --- a/tests/test_iam/test_iam_groups.py +++ b/tests/test_iam/test_iam_groups.py @@ -10,6 +10,7 @@ from nose.tools import assert_raises from boto.exception import BotoServerError from botocore.exceptions import ClientError from moto import mock_iam, mock_iam_deprecated +from moto.core import ACCOUNT_ID MOCK_POLICY = """ { @@ -51,16 +52,15 @@ def test_get_group_current(): assert result["Group"]["GroupName"] == "my-group" assert isinstance(result["Group"]["CreateDate"], datetime) assert result["Group"]["GroupId"] - assert result["Group"]["Arn"] == "arn:aws:iam::123456789012:group/my-group" + assert result["Group"]["Arn"] == "arn:aws:iam::{}:group/my-group".format(ACCOUNT_ID) assert not result["Users"] # Make a group with a different path: other_group = conn.create_group(GroupName="my-other-group", Path="some/location") assert other_group["Group"]["Path"] == "some/location" - assert ( - other_group["Group"]["Arn"] - == "arn:aws:iam::123456789012:group/some/location/my-other-group" - ) + assert other_group["Group"][ + "Arn" + ] == "arn:aws:iam::{}:group/some/location/my-other-group".format(ACCOUNT_ID) @mock_iam_deprecated() diff --git a/tests/test_kinesis/test_firehose.py b/tests/test_kinesis/test_firehose.py index 7101c4eaf..5e8c4aa08 100644 --- a/tests/test_kinesis/test_firehose.py +++ b/tests/test_kinesis/test_firehose.py @@ -7,6 +7,7 @@ import boto3 import sure # noqa from moto import mock_kinesis +from moto.core import ACCOUNT_ID def create_s3_delivery_stream(client, stream_name): @@ -14,7 +15,7 @@ def create_s3_delivery_stream(client, stream_name): DeliveryStreamName=stream_name, DeliveryStreamType="DirectPut", ExtendedS3DestinationConfiguration={ - "RoleARN": "arn:aws:iam::123456789012:role/firehose_delivery_role", + "RoleARN": "arn:aws:iam::{}:role/firehose_delivery_role".format(ACCOUNT_ID), "BucketARN": "arn:aws:s3:::kinesis-test", "Prefix": "myFolder/", "CompressionFormat": "UNCOMPRESSED", @@ -26,7 +27,9 @@ def create_s3_delivery_stream(client, stream_name): }, "SchemaConfiguration": { "DatabaseName": stream_name, - "RoleARN": "arn:aws:iam::123456789012:role/firehose_delivery_role", + "RoleARN": "arn:aws:iam::{}:role/firehose_delivery_role".format( + ACCOUNT_ID + ), "TableName": "outputTable", }, }, @@ -38,7 +41,7 @@ def create_redshift_delivery_stream(client, stream_name): return client.create_delivery_stream( DeliveryStreamName=stream_name, RedshiftDestinationConfiguration={ - "RoleARN": "arn:aws:iam::123456789012:role/firehose_delivery_role", + "RoleARN": "arn:aws:iam::{}:role/firehose_delivery_role".format(ACCOUNT_ID), "ClusterJDBCURL": "jdbc:redshift://host.amazonaws.com:5439/database", "CopyCommand": { "DataTableName": "outputTable", @@ -47,7 +50,9 @@ def create_redshift_delivery_stream(client, stream_name): "Username": "username", "Password": "password", "S3Configuration": { - "RoleARN": "arn:aws:iam::123456789012:role/firehose_delivery_role", + "RoleARN": "arn:aws:iam::{}:role/firehose_delivery_role".format( + ACCOUNT_ID + ), "BucketARN": "arn:aws:s3:::kinesis-test", "Prefix": "myFolder/", "BufferingHints": {"SizeInMBs": 123, "IntervalInSeconds": 124}, @@ -81,7 +86,9 @@ def test_create_redshift_delivery_stream(): { "DestinationId": "string", "RedshiftDestinationDescription": { - "RoleARN": "arn:aws:iam::123456789012:role/firehose_delivery_role", + "RoleARN": "arn:aws:iam::{}:role/firehose_delivery_role".format( + ACCOUNT_ID + ), "ClusterJDBCURL": "jdbc:redshift://host.amazonaws.com:5439/database", "CopyCommand": { "DataTableName": "outputTable", @@ -89,7 +96,9 @@ def test_create_redshift_delivery_stream(): }, "Username": "username", "S3DestinationDescription": { - "RoleARN": "arn:aws:iam::123456789012:role/firehose_delivery_role", + "RoleARN": "arn:aws:iam::{}:role/firehose_delivery_role".format( + ACCOUNT_ID + ), "BucketARN": "arn:aws:s3:::kinesis-test", "Prefix": "myFolder/", "BufferingHints": { @@ -130,7 +139,9 @@ def test_create_s3_delivery_stream(): { "DestinationId": "string", "ExtendedS3DestinationDescription": { - "RoleARN": "arn:aws:iam::123456789012:role/firehose_delivery_role", + "RoleARN": "arn:aws:iam::{}:role/firehose_delivery_role".format( + ACCOUNT_ID + ), "BucketARN": "arn:aws:s3:::kinesis-test", "Prefix": "myFolder/", "CompressionFormat": "UNCOMPRESSED", @@ -146,7 +157,9 @@ def test_create_s3_delivery_stream(): }, "SchemaConfiguration": { "DatabaseName": "stream1", - "RoleARN": "arn:aws:iam::123456789012:role/firehose_delivery_role", + "RoleARN": "arn:aws:iam::{}:role/firehose_delivery_role".format( + ACCOUNT_ID + ), "TableName": "outputTable", }, }, @@ -165,7 +178,7 @@ def test_create_stream_without_redshift(): response = client.create_delivery_stream( DeliveryStreamName="stream1", S3DestinationConfiguration={ - "RoleARN": "arn:aws:iam::123456789012:role/firehose_delivery_role", + "RoleARN": "arn:aws:iam::{}:role/firehose_delivery_role".format(ACCOUNT_ID), "BucketARN": "arn:aws:s3:::kinesis-test", "Prefix": "myFolder/", "BufferingHints": {"SizeInMBs": 123, "IntervalInSeconds": 124}, @@ -191,8 +204,12 @@ def test_create_stream_without_redshift(): { "DestinationId": "string", "S3DestinationDescription": { - "RoleARN": "arn:aws:iam::123456789012:role/firehose_delivery_role", - "RoleARN": "arn:aws:iam::123456789012:role/firehose_delivery_role", + "RoleARN": "arn:aws:iam::{}:role/firehose_delivery_role".format( + ACCOUNT_ID + ), + "RoleARN": "arn:aws:iam::{}:role/firehose_delivery_role".format( + ACCOUNT_ID + ), "BucketARN": "arn:aws:s3:::kinesis-test", "Prefix": "myFolder/", "BufferingHints": {"SizeInMBs": 123, "IntervalInSeconds": 124}, diff --git a/tests/test_kinesis/test_kinesis.py b/tests/test_kinesis/test_kinesis.py index 308100d8b..de1764892 100644 --- a/tests/test_kinesis/test_kinesis.py +++ b/tests/test_kinesis/test_kinesis.py @@ -8,6 +8,7 @@ import boto3 from boto.kinesis.exceptions import ResourceNotFoundException, InvalidArgumentException from moto import mock_kinesis, mock_kinesis_deprecated +from moto.core import ACCOUNT_ID @mock_kinesis_deprecated @@ -21,7 +22,9 @@ def test_create_cluster(): stream = stream_response["StreamDescription"] stream["StreamName"].should.equal("my_stream") stream["HasMoreShards"].should.equal(False) - stream["StreamARN"].should.equal("arn:aws:kinesis:us-west-2:123456789012:my_stream") + stream["StreamARN"].should.equal( + "arn:aws:kinesis:us-west-2:{}:my_stream".format(ACCOUNT_ID) + ) stream["StreamStatus"].should.equal("ACTIVE") shards = stream["Shards"] @@ -87,7 +90,7 @@ def test_describe_stream_summary(): stream["StreamName"].should.equal(stream_name) stream["OpenShardCount"].should.equal(shard_count) stream["StreamARN"].should.equal( - "arn:aws:kinesis:us-west-2:123456789012:{}".format(stream_name) + "arn:aws:kinesis:us-west-2:{}:{}".format(ACCOUNT_ID, stream_name) ) stream["StreamStatus"].should.equal("ACTIVE") diff --git a/tests/test_redshift/test_redshift.py b/tests/test_redshift/test_redshift.py index 528eaa5e0..6bb3b1396 100644 --- a/tests/test_redshift/test_redshift.py +++ b/tests/test_redshift/test_redshift.py @@ -18,6 +18,7 @@ from moto import mock_ec2 from moto import mock_ec2_deprecated from moto import mock_redshift from moto import mock_redshift_deprecated +from moto.core import ACCOUNT_ID @mock_redshift @@ -998,12 +999,12 @@ def test_create_cluster_status_update(): def test_describe_tags_with_resource_type(): client = boto3.client("redshift", region_name="us-east-1") cluster_identifier = "my_cluster" - cluster_arn = "arn:aws:redshift:us-east-1:123456789012:" "cluster:{}".format( - cluster_identifier + cluster_arn = "arn:aws:redshift:us-east-1:{}:" "cluster:{}".format( + ACCOUNT_ID, cluster_identifier ) snapshot_identifier = "my_snapshot" - snapshot_arn = "arn:aws:redshift:us-east-1:123456789012:" "snapshot:{}/{}".format( - cluster_identifier, snapshot_identifier + snapshot_arn = "arn:aws:redshift:us-east-1:{}:" "snapshot:{}/{}".format( + ACCOUNT_ID, cluster_identifier, snapshot_identifier ) tag_key = "test-tag-key" tag_value = "test-tag-value" @@ -1044,7 +1045,9 @@ def test_describe_tags_with_resource_type(): @mock_redshift def test_describe_tags_cannot_specify_resource_type_and_resource_name(): client = boto3.client("redshift", region_name="us-east-1") - resource_name = "arn:aws:redshift:us-east-1:123456789012:cluster:cluster-id" + resource_name = "arn:aws:redshift:us-east-1:{}:cluster:cluster-id".format( + ACCOUNT_ID + ) resource_type = "cluster" client.describe_tags.when.called_with( ResourceName=resource_name, ResourceType=resource_type @@ -1055,12 +1058,12 @@ def test_describe_tags_cannot_specify_resource_type_and_resource_name(): def test_describe_tags_with_resource_name(): client = boto3.client("redshift", region_name="us-east-1") cluster_identifier = "cluster-id" - cluster_arn = "arn:aws:redshift:us-east-1:123456789012:" "cluster:{}".format( - cluster_identifier + cluster_arn = "arn:aws:redshift:us-east-1:{}:" "cluster:{}".format( + ACCOUNT_ID, cluster_identifier ) snapshot_identifier = "snapshot-id" - snapshot_arn = "arn:aws:redshift:us-east-1:123456789012:" "snapshot:{}/{}".format( - cluster_identifier, snapshot_identifier + snapshot_arn = "arn:aws:redshift:us-east-1:{}:" "snapshot:{}/{}".format( + ACCOUNT_ID, cluster_identifier, snapshot_identifier ) tag_key = "test-tag-key" tag_value = "test-tag-value" @@ -1102,8 +1105,8 @@ def test_describe_tags_with_resource_name(): def test_create_tags(): client = boto3.client("redshift", region_name="us-east-1") cluster_identifier = "cluster-id" - cluster_arn = "arn:aws:redshift:us-east-1:123456789012:" "cluster:{}".format( - cluster_identifier + cluster_arn = "arn:aws:redshift:us-east-1:{}:" "cluster:{}".format( + ACCOUNT_ID, cluster_identifier ) tag_key = "test-tag-key" tag_value = "test-tag-value" @@ -1133,8 +1136,8 @@ def test_create_tags(): def test_delete_tags(): client = boto3.client("redshift", region_name="us-east-1") cluster_identifier = "cluster-id" - cluster_arn = "arn:aws:redshift:us-east-1:123456789012:" "cluster:{}".format( - cluster_identifier + cluster_arn = "arn:aws:redshift:us-east-1:{}:" "cluster:{}".format( + ACCOUNT_ID, cluster_identifier ) tag_key = "test-tag-key" tag_value = "test-tag-value" diff --git a/tests/test_s3/test_s3.py b/tests/test_s3/test_s3.py index 8f3c3538c..682213d13 100644 --- a/tests/test_s3/test_s3.py +++ b/tests/test_s3/test_s3.py @@ -32,9 +32,10 @@ from nose.tools import assert_raises import sure # noqa -from moto import settings, mock_s3, mock_s3_deprecated +from moto import settings, mock_s3, mock_s3_deprecated, mock_config import moto.s3.models as s3model from moto.core.exceptions import InvalidNextTokenException +from moto.core.utils import py2_strip_unicode_keys if settings.TEST_SERVER_MODE: REDUCED_PART_SIZE = s3model.UPLOAD_PART_MIN_SIZE @@ -1260,7 +1261,7 @@ def test_boto3_list_objects_truncated_response(): assert listed_object["Key"] == "one" assert resp["MaxKeys"] == 1 assert resp["IsTruncated"] == True - assert resp["Prefix"] == "None" + assert resp.get("Prefix") is None assert resp["Delimiter"] == "None" assert "NextMarker" in resp @@ -1273,7 +1274,7 @@ def test_boto3_list_objects_truncated_response(): assert listed_object["Key"] == "three" assert resp["MaxKeys"] == 1 assert resp["IsTruncated"] == True - assert resp["Prefix"] == "None" + assert resp.get("Prefix") is None assert resp["Delimiter"] == "None" assert "NextMarker" in resp @@ -1286,7 +1287,7 @@ def test_boto3_list_objects_truncated_response(): assert listed_object["Key"] == "two" assert resp["MaxKeys"] == 1 assert resp["IsTruncated"] == False - assert resp["Prefix"] == "None" + assert resp.get("Prefix") is None assert resp["Delimiter"] == "None" assert "NextMarker" not in resp @@ -3278,6 +3279,148 @@ def test_delete_objects_with_url_encoded_key(key): assert_deleted() +@mock_s3 +@mock_config +def test_public_access_block(): + client = boto3.client("s3") + client.create_bucket(Bucket="mybucket") + + # Try to get the public access block (should not exist by default) + with assert_raises(ClientError) as ce: + client.get_public_access_block(Bucket="mybucket") + + assert ( + ce.exception.response["Error"]["Code"] == "NoSuchPublicAccessBlockConfiguration" + ) + assert ( + ce.exception.response["Error"]["Message"] + == "The public access block configuration was not found" + ) + assert ce.exception.response["ResponseMetadata"]["HTTPStatusCode"] == 404 + + # Put a public block in place: + test_map = { + "BlockPublicAcls": False, + "IgnorePublicAcls": False, + "BlockPublicPolicy": False, + "RestrictPublicBuckets": False, + } + + for field in test_map.keys(): + # Toggle: + test_map[field] = True + + client.put_public_access_block( + Bucket="mybucket", PublicAccessBlockConfiguration=test_map + ) + + # Test: + assert ( + test_map + == client.get_public_access_block(Bucket="mybucket")[ + "PublicAccessBlockConfiguration" + ] + ) + + # Assume missing values are default False: + client.put_public_access_block( + Bucket="mybucket", PublicAccessBlockConfiguration={"BlockPublicAcls": True} + ) + assert client.get_public_access_block(Bucket="mybucket")[ + "PublicAccessBlockConfiguration" + ] == { + "BlockPublicAcls": True, + "IgnorePublicAcls": False, + "BlockPublicPolicy": False, + "RestrictPublicBuckets": False, + } + + # Test with a blank PublicAccessBlockConfiguration: + with assert_raises(ClientError) as ce: + client.put_public_access_block( + Bucket="mybucket", PublicAccessBlockConfiguration={} + ) + + assert ce.exception.response["Error"]["Code"] == "InvalidRequest" + assert ( + ce.exception.response["Error"]["Message"] + == "Must specify at least one configuration." + ) + assert ce.exception.response["ResponseMetadata"]["HTTPStatusCode"] == 400 + + # Test that things work with AWS Config: + config_client = boto3.client("config", region_name="us-east-1") + result = config_client.get_resource_config_history( + resourceType="AWS::S3::Bucket", resourceId="mybucket" + ) + pub_block_config = json.loads( + result["configurationItems"][0]["supplementaryConfiguration"][ + "PublicAccessBlockConfiguration" + ] + ) + + assert pub_block_config == { + "blockPublicAcls": True, + "ignorePublicAcls": False, + "blockPublicPolicy": False, + "restrictPublicBuckets": False, + } + + # Delete: + client.delete_public_access_block(Bucket="mybucket") + + with assert_raises(ClientError) as ce: + client.get_public_access_block(Bucket="mybucket") + assert ( + ce.exception.response["Error"]["Code"] == "NoSuchPublicAccessBlockConfiguration" + ) + + +@mock_s3 +def test_s3_public_access_block_to_config_dict(): + from moto.s3.config import s3_config_query + + # With 1 bucket in us-west-2: + s3_config_query.backends["global"].create_bucket("bucket1", "us-west-2") + + public_access_block = { + "BlockPublicAcls": "True", + "IgnorePublicAcls": "False", + "BlockPublicPolicy": "True", + "RestrictPublicBuckets": "False", + } + + # Python 2 unicode issues: + if sys.version_info[0] < 3: + public_access_block = py2_strip_unicode_keys(public_access_block) + + # Add a public access block: + s3_config_query.backends["global"].put_bucket_public_access_block( + "bucket1", public_access_block + ) + + result = ( + s3_config_query.backends["global"] + .buckets["bucket1"] + .public_access_block.to_config_dict() + ) + + convert_bool = lambda x: x == "True" + for key, value in public_access_block.items(): + assert result[ + "{lowercase}{rest}".format(lowercase=key[0].lower(), rest=key[1:]) + ] == convert_bool(value) + + # Verify that this resides in the full bucket's to_config_dict: + full_result = s3_config_query.backends["global"].buckets["bucket1"].to_config_dict() + assert ( + json.loads( + full_result["supplementaryConfiguration"]["PublicAccessBlockConfiguration"] + ) + == result + ) + + @mock_s3 def test_list_config_discovered_resources(): from moto.s3.config import s3_config_query diff --git a/tests/test_ses/test_ses_sns_boto3.py b/tests/test_ses/test_ses_sns_boto3.py index a55c150ff..fc58d88aa 100644 --- a/tests/test_ses/test_ses_sns_boto3.py +++ b/tests/test_ses/test_ses_sns_boto3.py @@ -10,6 +10,7 @@ import sure # noqa from nose import tools from moto import mock_ses, mock_sns, mock_sqs from moto.ses.models import SESFeedback +from moto.core import ACCOUNT_ID @mock_ses @@ -35,7 +36,7 @@ def __setup_feedback_env__( sns_conn.subscribe( TopicArn=topic_arn, Protocol="sqs", - Endpoint="arn:aws:sqs:%s:123456789012:%s" % (region, queue), + Endpoint="arn:aws:sqs:%s:%s:%s" % (region, ACCOUNT_ID, queue), ) # Verify SES domain ses_conn.verify_domain_identity(Domain=domain) diff --git a/tests/test_sns/test_application.py b/tests/test_sns/test_application.py index efa5e0f3e..e4fe93d53 100644 --- a/tests/test_sns/test_application.py +++ b/tests/test_sns/test_application.py @@ -3,6 +3,7 @@ from __future__ import unicode_literals import boto from boto.exception import BotoServerError from moto import mock_sns_deprecated +from moto.core import ACCOUNT_ID import sure # noqa @@ -21,7 +22,7 @@ def test_create_platform_application(): "CreatePlatformApplicationResult" ]["PlatformApplicationArn"] application_arn.should.equal( - "arn:aws:sns:us-east-1:123456789012:app/APNS/my-application" + "arn:aws:sns:us-east-1:{}:app/APNS/my-application".format(ACCOUNT_ID) ) @@ -137,7 +138,7 @@ def test_create_platform_endpoint(): "CreatePlatformEndpointResult" ]["EndpointArn"] endpoint_arn.should.contain( - "arn:aws:sns:us-east-1:123456789012:endpoint/APNS/my-application/" + "arn:aws:sns:us-east-1:{}:endpoint/APNS/my-application/".format(ACCOUNT_ID) ) diff --git a/tests/test_sns/test_application_boto3.py b/tests/test_sns/test_application_boto3.py index 6f683b051..6f9be2926 100644 --- a/tests/test_sns/test_application_boto3.py +++ b/tests/test_sns/test_application_boto3.py @@ -4,6 +4,7 @@ import boto3 from botocore.exceptions import ClientError from moto import mock_sns import sure # noqa +from moto.core import ACCOUNT_ID @mock_sns @@ -19,7 +20,7 @@ def test_create_platform_application(): ) application_arn = response["PlatformApplicationArn"] application_arn.should.equal( - "arn:aws:sns:us-east-1:123456789012:app/APNS/my-application" + "arn:aws:sns:us-east-1:{}:app/APNS/my-application".format(ACCOUNT_ID) ) @@ -131,7 +132,7 @@ def test_create_platform_endpoint(): endpoint_arn = endpoint["EndpointArn"] endpoint_arn.should.contain( - "arn:aws:sns:us-east-1:123456789012:endpoint/APNS/my-application/" + "arn:aws:sns:us-east-1:{}:endpoint/APNS/my-application/".format(ACCOUNT_ID) ) diff --git a/tests/test_sns/test_publishing.py b/tests/test_sns/test_publishing.py index b45277bde..30fa80f15 100644 --- a/tests/test_sns/test_publishing.py +++ b/tests/test_sns/test_publishing.py @@ -7,9 +7,15 @@ from freezegun import freeze_time import sure # noqa from moto import mock_sns_deprecated, mock_sqs_deprecated +from moto.core import ACCOUNT_ID - -MESSAGE_FROM_SQS_TEMPLATE = '{\n "Message": "%s",\n "MessageId": "%s",\n "Signature": "EXAMPLElDMXvB8r9R83tGoNn0ecwd5UjllzsvSvbItzfaMpN2nk5HVSw7XnOn/49IkxDKz8YrlH2qJXj2iZB0Zo2O71c4qQk1fMUDi3LGpij7RCW7AW9vYYsSqIKRnFS94ilu7NFhUzLiieYr4BKHpdTmdD6c0esKEYBpabxDSc=",\n "SignatureVersion": "1",\n "SigningCertURL": "https://sns.us-east-1.amazonaws.com/SimpleNotificationService-f3ecfb7224c7233fe7bb5f59f96de52f.pem",\n "Subject": "%s",\n "Timestamp": "2015-01-01T12:00:00.000Z",\n "TopicArn": "arn:aws:sns:%s:123456789012:some-topic",\n "Type": "Notification",\n "UnsubscribeURL": "https://sns.us-east-1.amazonaws.com/?Action=Unsubscribe&SubscriptionArn=arn:aws:sns:us-east-1:123456789012:some-topic:2bcfbf39-05c3-41de-beaa-fcfcc21c8f55"\n}' +MESSAGE_FROM_SQS_TEMPLATE = ( + '{\n "Message": "%s",\n "MessageId": "%s",\n "Signature": "EXAMPLElDMXvB8r9R83tGoNn0ecwd5UjllzsvSvbItzfaMpN2nk5HVSw7XnOn/49IkxDKz8YrlH2qJXj2iZB0Zo2O71c4qQk1fMUDi3LGpij7RCW7AW9vYYsSqIKRnFS94ilu7NFhUzLiieYr4BKHpdTmdD6c0esKEYBpabxDSc=",\n "SignatureVersion": "1",\n "SigningCertURL": "https://sns.us-east-1.amazonaws.com/SimpleNotificationService-f3ecfb7224c7233fe7bb5f59f96de52f.pem",\n "Subject": "%s",\n "Timestamp": "2015-01-01T12:00:00.000Z",\n "TopicArn": "arn:aws:sns:%s:' + + ACCOUNT_ID + + ':some-topic",\n "Type": "Notification",\n "UnsubscribeURL": "https://sns.us-east-1.amazonaws.com/?Action=Unsubscribe&SubscriptionArn=arn:aws:sns:us-east-1:' + + ACCOUNT_ID + + ':some-topic:2bcfbf39-05c3-41de-beaa-fcfcc21c8f55"\n}' +) @mock_sqs_deprecated @@ -25,7 +31,9 @@ def test_publish_to_sqs(): sqs_conn = boto.connect_sqs() sqs_conn.create_queue("test-queue") - conn.subscribe(topic_arn, "sqs", "arn:aws:sqs:us-east-1:123456789012:test-queue") + conn.subscribe( + topic_arn, "sqs", "arn:aws:sqs:us-east-1:{}:test-queue".format(ACCOUNT_ID) + ) message_to_publish = "my message" subject_to_publish = "test subject" @@ -66,7 +74,9 @@ def test_publish_to_sqs_in_different_region(): sqs_conn = boto.sqs.connect_to_region("us-west-2") sqs_conn.create_queue("test-queue") - conn.subscribe(topic_arn, "sqs", "arn:aws:sqs:us-west-2:123456789012:test-queue") + conn.subscribe( + topic_arn, "sqs", "arn:aws:sqs:us-west-2:{}:test-queue".format(ACCOUNT_ID) + ) message_to_publish = "my message" subject_to_publish = "test subject" diff --git a/tests/test_sns/test_publishing_boto3.py b/tests/test_sns/test_publishing_boto3.py index 5bda0720c..d85c8fefe 100644 --- a/tests/test_sns/test_publishing_boto3.py +++ b/tests/test_sns/test_publishing_boto3.py @@ -12,9 +12,15 @@ import responses from botocore.exceptions import ClientError from nose.tools import assert_raises from moto import mock_sns, mock_sqs +from moto.core import ACCOUNT_ID - -MESSAGE_FROM_SQS_TEMPLATE = '{\n "Message": "%s",\n "MessageId": "%s",\n "Signature": "EXAMPLElDMXvB8r9R83tGoNn0ecwd5UjllzsvSvbItzfaMpN2nk5HVSw7XnOn/49IkxDKz8YrlH2qJXj2iZB0Zo2O71c4qQk1fMUDi3LGpij7RCW7AW9vYYsSqIKRnFS94ilu7NFhUzLiieYr4BKHpdTmdD6c0esKEYBpabxDSc=",\n "SignatureVersion": "1",\n "SigningCertURL": "https://sns.us-east-1.amazonaws.com/SimpleNotificationService-f3ecfb7224c7233fe7bb5f59f96de52f.pem",\n "Subject": "my subject",\n "Timestamp": "2015-01-01T12:00:00.000Z",\n "TopicArn": "arn:aws:sns:%s:123456789012:some-topic",\n "Type": "Notification",\n "UnsubscribeURL": "https://sns.us-east-1.amazonaws.com/?Action=Unsubscribe&SubscriptionArn=arn:aws:sns:us-east-1:123456789012:some-topic:2bcfbf39-05c3-41de-beaa-fcfcc21c8f55"\n}' +MESSAGE_FROM_SQS_TEMPLATE = ( + '{\n "Message": "%s",\n "MessageId": "%s",\n "Signature": "EXAMPLElDMXvB8r9R83tGoNn0ecwd5UjllzsvSvbItzfaMpN2nk5HVSw7XnOn/49IkxDKz8YrlH2qJXj2iZB0Zo2O71c4qQk1fMUDi3LGpij7RCW7AW9vYYsSqIKRnFS94ilu7NFhUzLiieYr4BKHpdTmdD6c0esKEYBpabxDSc=",\n "SignatureVersion": "1",\n "SigningCertURL": "https://sns.us-east-1.amazonaws.com/SimpleNotificationService-f3ecfb7224c7233fe7bb5f59f96de52f.pem",\n "Subject": "my subject",\n "Timestamp": "2015-01-01T12:00:00.000Z",\n "TopicArn": "arn:aws:sns:%s:' + + ACCOUNT_ID + + ':some-topic",\n "Type": "Notification",\n "UnsubscribeURL": "https://sns.us-east-1.amazonaws.com/?Action=Unsubscribe&SubscriptionArn=arn:aws:sns:us-east-1:' + + ACCOUNT_ID + + ':some-topic:2bcfbf39-05c3-41de-beaa-fcfcc21c8f55"\n}' +) @mock_sqs @@ -31,7 +37,7 @@ def test_publish_to_sqs(): conn.subscribe( TopicArn=topic_arn, Protocol="sqs", - Endpoint="arn:aws:sqs:us-east-1:123456789012:test-queue", + Endpoint="arn:aws:sqs:us-east-1:{}:test-queue".format(ACCOUNT_ID), ) message = "my message" with freeze_time("2015-01-01 12:00:00"): @@ -88,7 +94,7 @@ def test_publish_to_sqs_bad(): conn.subscribe( TopicArn=topic_arn, Protocol="sqs", - Endpoint="arn:aws:sqs:us-east-1:123456789012:test-queue", + Endpoint="arn:aws:sqs:us-east-1:{}:test-queue".format(ACCOUNT_ID), ) message = "my message" try: @@ -149,7 +155,7 @@ def test_publish_to_sqs_msg_attr_byte_value(): conn.subscribe( TopicArn=topic_arn, Protocol="sqs", - Endpoint="arn:aws:sqs:us-east-1:123456789012:test-queue", + Endpoint="arn:aws:sqs:us-east-1:{}:test-queue".format(ACCOUNT_ID), ) message = "my message" conn.publish( @@ -243,7 +249,7 @@ def test_publish_to_sqs_dump_json(): conn.subscribe( TopicArn=topic_arn, Protocol="sqs", - Endpoint="arn:aws:sqs:us-east-1:123456789012:test-queue", + Endpoint="arn:aws:sqs:us-east-1:{}:test-queue".format(ACCOUNT_ID), ) message = json.dumps( @@ -289,7 +295,7 @@ def test_publish_to_sqs_in_different_region(): conn.subscribe( TopicArn=topic_arn, Protocol="sqs", - Endpoint="arn:aws:sqs:us-west-2:123456789012:test-queue", + Endpoint="arn:aws:sqs:us-west-2:{}:test-queue".format(ACCOUNT_ID), ) message = "my message" @@ -348,7 +354,7 @@ def test_publish_subject(): conn.subscribe( TopicArn=topic_arn, Protocol="sqs", - Endpoint="arn:aws:sqs:us-east-1:123456789012:test-queue", + Endpoint="arn:aws:sqs:us-east-1:{}:test-queue".format(ACCOUNT_ID), ) message = "my message" subject1 = "test subject" diff --git a/tests/test_sns/test_server.py b/tests/test_sns/test_server.py index ec8bbe201..78bc147df 100644 --- a/tests/test_sns/test_server.py +++ b/tests/test_sns/test_server.py @@ -1,4 +1,5 @@ from __future__ import unicode_literals +from moto.core import ACCOUNT_ID import sure # noqa @@ -16,11 +17,11 @@ def test_sns_server_get(): topic_data = test_client.action_data("CreateTopic", Name="testtopic") topic_data.should.contain("CreateTopicResult") topic_data.should.contain( - "arn:aws:sns:us-east-1:123456789012:testtopic" + "arn:aws:sns:us-east-1:{}:testtopic".format(ACCOUNT_ID) ) topics_data = test_client.action_data("ListTopics") topics_data.should.contain("ListTopicsResult") topic_data.should.contain( - "arn:aws:sns:us-east-1:123456789012:testtopic" + "arn:aws:sns:us-east-1:{}:testtopic".format(ACCOUNT_ID) ) diff --git a/tests/test_sns/test_subscriptions.py b/tests/test_sns/test_subscriptions.py index fbd4274f4..f773438d7 100644 --- a/tests/test_sns/test_subscriptions.py +++ b/tests/test_sns/test_subscriptions.py @@ -54,9 +54,10 @@ def test_deleting_subscriptions_by_deleting_topic(): ]["Subscriptions"] subscriptions.should.have.length_of(1) subscription = subscriptions[0] + subscription_arn = subscription["SubscriptionArn"] subscription["TopicArn"].should.equal(topic_arn) subscription["Protocol"].should.equal("http") - subscription["SubscriptionArn"].should.contain(topic_arn) + subscription_arn.should.contain(topic_arn) subscription["Endpoint"].should.equal("http://example.com/") # Now delete the topic @@ -67,12 +68,25 @@ def test_deleting_subscriptions_by_deleting_topic(): topics = topics_json["ListTopicsResponse"]["ListTopicsResult"]["Topics"] topics.should.have.length_of(0) - # And there should be zero subscriptions left + # And the subscription should still be left + subscriptions = conn.get_all_subscriptions()["ListSubscriptionsResponse"][ + "ListSubscriptionsResult" + ]["Subscriptions"] + subscriptions.should.have.length_of(1) + subscription = subscriptions[0] + subscription["SubscriptionArn"].should.equal(subscription_arn) + + # Now delete hanging subscription + conn.unsubscribe(subscription_arn) + subscriptions = conn.get_all_subscriptions()["ListSubscriptionsResponse"][ "ListSubscriptionsResult" ]["Subscriptions"] subscriptions.should.have.length_of(0) + # Deleting it again should not result in any error + conn.unsubscribe(subscription_arn) + @mock_sns_deprecated def test_getting_subscriptions_by_topic(): diff --git a/tests/test_sns/test_subscriptions_boto3.py b/tests/test_sns/test_subscriptions_boto3.py index 6aab6b369..d91b3566b 100644 --- a/tests/test_sns/test_subscriptions_boto3.py +++ b/tests/test_sns/test_subscriptions_boto3.py @@ -8,7 +8,11 @@ from botocore.exceptions import ClientError from nose.tools import assert_raises from moto import mock_sns -from moto.sns.models import DEFAULT_PAGE_SIZE +from moto.sns.models import ( + DEFAULT_PAGE_SIZE, + DEFAULT_EFFECTIVE_DELIVERY_POLICY, + DEFAULT_ACCOUNT_ID, +) @mock_sns @@ -93,34 +97,48 @@ def test_creating_subscription(): @mock_sns -def test_deleting_subscriptions_by_deleting_topic(): - conn = boto3.client("sns", region_name="us-east-1") - conn.create_topic(Name="some-topic") - response = conn.list_topics() +def test_unsubscribe_from_deleted_topic(): + client = boto3.client("sns", region_name="us-east-1") + client.create_topic(Name="some-topic") + response = client.list_topics() topic_arn = response["Topics"][0]["TopicArn"] - conn.subscribe(TopicArn=topic_arn, Protocol="http", Endpoint="http://example.com/") + client.subscribe( + TopicArn=topic_arn, Protocol="http", Endpoint="http://example.com/" + ) - subscriptions = conn.list_subscriptions()["Subscriptions"] + subscriptions = client.list_subscriptions()["Subscriptions"] subscriptions.should.have.length_of(1) subscription = subscriptions[0] + subscription_arn = subscription["SubscriptionArn"] subscription["TopicArn"].should.equal(topic_arn) subscription["Protocol"].should.equal("http") - subscription["SubscriptionArn"].should.contain(topic_arn) + subscription_arn.should.contain(topic_arn) subscription["Endpoint"].should.equal("http://example.com/") # Now delete the topic - conn.delete_topic(TopicArn=topic_arn) + client.delete_topic(TopicArn=topic_arn) # And there should now be 0 topics - topics_json = conn.list_topics() + topics_json = client.list_topics() topics = topics_json["Topics"] topics.should.have.length_of(0) - # And there should be zero subscriptions left - subscriptions = conn.list_subscriptions()["Subscriptions"] + # And the subscription should still be left + subscriptions = client.list_subscriptions()["Subscriptions"] + subscriptions.should.have.length_of(1) + subscription = subscriptions[0] + subscription["SubscriptionArn"].should.equal(subscription_arn) + + # Now delete hanging subscription + client.unsubscribe(SubscriptionArn=subscription_arn) + + subscriptions = client.list_subscriptions()["Subscriptions"] subscriptions.should.have.length_of(0) + # Deleting it again should not result in any error + client.unsubscribe(SubscriptionArn=subscription_arn) + @mock_sns def test_getting_subscriptions_by_topic(): @@ -195,21 +213,23 @@ def test_subscribe_attributes(): resp = client.subscribe(TopicArn=arn, Protocol="http", Endpoint="http://test.com") - attributes = client.get_subscription_attributes( + response = client.get_subscription_attributes( SubscriptionArn=resp["SubscriptionArn"] ) - attributes.should.contain("Attributes") - attributes["Attributes"].should.contain("PendingConfirmation") - attributes["Attributes"]["PendingConfirmation"].should.equal("false") - attributes["Attributes"].should.contain("Endpoint") - attributes["Attributes"]["Endpoint"].should.equal("http://test.com") - attributes["Attributes"].should.contain("TopicArn") - attributes["Attributes"]["TopicArn"].should.equal(arn) - attributes["Attributes"].should.contain("Protocol") - attributes["Attributes"]["Protocol"].should.equal("http") - attributes["Attributes"].should.contain("SubscriptionArn") - attributes["Attributes"]["SubscriptionArn"].should.equal(resp["SubscriptionArn"]) + response.should.contain("Attributes") + attributes = response["Attributes"] + attributes["PendingConfirmation"].should.equal("false") + attributes["ConfirmationWasAuthenticated"].should.equal("true") + attributes["Endpoint"].should.equal("http://test.com") + attributes["TopicArn"].should.equal(arn) + attributes["Protocol"].should.equal("http") + attributes["SubscriptionArn"].should.equal(resp["SubscriptionArn"]) + attributes["Owner"].should.equal(str(DEFAULT_ACCOUNT_ID)) + attributes["RawMessageDelivery"].should.equal("false") + json.loads(attributes["EffectiveDeliveryPolicy"]).should.equal( + DEFAULT_EFFECTIVE_DELIVERY_POLICY + ) @mock_sns diff --git a/tests/test_sns/test_topics.py b/tests/test_sns/test_topics.py index 4a5100c94..e91ab6e2d 100644 --- a/tests/test_sns/test_topics.py +++ b/tests/test_sns/test_topics.py @@ -8,6 +8,7 @@ import sure # noqa from boto.exception import BotoServerError from moto import mock_sns_deprecated from moto.sns.models import DEFAULT_EFFECTIVE_DELIVERY_POLICY, DEFAULT_PAGE_SIZE +from moto.core import ACCOUNT_ID @mock_sns_deprecated @@ -19,7 +20,7 @@ def test_create_and_delete_topic(): topics = topics_json["ListTopicsResponse"]["ListTopicsResult"]["Topics"] topics.should.have.length_of(1) topics[0]["TopicArn"].should.equal( - "arn:aws:sns:{0}:123456789012:some-topic".format(conn.region.name) + "arn:aws:sns:{0}:{1}:some-topic".format(conn.region.name, ACCOUNT_ID) ) # Delete the topic @@ -58,7 +59,9 @@ def test_topic_corresponds_to_region(): topic_arn = topics_json["ListTopicsResponse"]["ListTopicsResult"]["Topics"][0][ "TopicArn" ] - topic_arn.should.equal("arn:aws:sns:{0}:123456789012:some-topic".format(region)) + topic_arn.should.equal( + "arn:aws:sns:{0}:{1}:some-topic".format(region, ACCOUNT_ID) + ) @mock_sns_deprecated @@ -75,9 +78,9 @@ def test_topic_attributes(): "GetTopicAttributesResult" ]["Attributes"] attributes["TopicArn"].should.equal( - "arn:aws:sns:{0}:123456789012:some-topic".format(conn.region.name) + "arn:aws:sns:{0}:{1}:some-topic".format(conn.region.name, ACCOUNT_ID) ) - attributes["Owner"].should.equal(123456789012) + attributes["Owner"].should.equal(ACCOUNT_ID) json.loads(attributes["Policy"]).should.equal( { "Version": "2008-10-17", @@ -98,8 +101,10 @@ def test_topic_attributes(): "SNS:Publish", "SNS:Receive", ], - "Resource": "arn:aws:sns:us-east-1:123456789012:some-topic", - "Condition": {"StringEquals": {"AWS:SourceOwner": "123456789012"}}, + "Resource": "arn:aws:sns:us-east-1:{}:some-topic".format( + ACCOUNT_ID + ), + "Condition": {"StringEquals": {"AWS:SourceOwner": ACCOUNT_ID}}, } ], } diff --git a/tests/test_sns/test_topics_boto3.py b/tests/test_sns/test_topics_boto3.py index e4c9d303f..87800bd84 100644 --- a/tests/test_sns/test_topics_boto3.py +++ b/tests/test_sns/test_topics_boto3.py @@ -8,6 +8,7 @@ import sure # noqa from botocore.exceptions import ClientError from moto import mock_sns from moto.sns.models import DEFAULT_EFFECTIVE_DELIVERY_POLICY, DEFAULT_PAGE_SIZE +from moto.core import ACCOUNT_ID @mock_sns @@ -20,8 +21,8 @@ def test_create_and_delete_topic(): topics = topics_json["Topics"] topics.should.have.length_of(1) topics[0]["TopicArn"].should.equal( - "arn:aws:sns:{0}:123456789012:{1}".format( - conn._client_config.region_name, topic_name + "arn:aws:sns:{0}:{1}:{2}".format( + conn._client_config.region_name, ACCOUNT_ID, topic_name ) ) @@ -132,7 +133,9 @@ def test_topic_corresponds_to_region(): conn.create_topic(Name="some-topic") topics_json = conn.list_topics() topic_arn = topics_json["Topics"][0]["TopicArn"] - topic_arn.should.equal("arn:aws:sns:{0}:123456789012:some-topic".format(region)) + topic_arn.should.equal( + "arn:aws:sns:{0}:{1}:some-topic".format(region, ACCOUNT_ID) + ) @mock_sns @@ -145,11 +148,11 @@ def test_topic_attributes(): attributes = conn.get_topic_attributes(TopicArn=topic_arn)["Attributes"] attributes["TopicArn"].should.equal( - "arn:aws:sns:{0}:123456789012:some-topic".format( - conn._client_config.region_name + "arn:aws:sns:{0}:{1}:some-topic".format( + conn._client_config.region_name, ACCOUNT_ID ) ) - attributes["Owner"].should.equal("123456789012") + attributes["Owner"].should.equal(ACCOUNT_ID) json.loads(attributes["Policy"]).should.equal( { "Version": "2008-10-17", @@ -170,8 +173,10 @@ def test_topic_attributes(): "SNS:Publish", "SNS:Receive", ], - "Resource": "arn:aws:sns:us-east-1:123456789012:some-topic", - "Condition": {"StringEquals": {"AWS:SourceOwner": "123456789012"}}, + "Resource": "arn:aws:sns:us-east-1:{}:some-topic".format( + ACCOUNT_ID + ), + "Condition": {"StringEquals": {"AWS:SourceOwner": ACCOUNT_ID}}, } ], } @@ -271,15 +276,19 @@ def test_add_remove_permissions(): "SNS:Publish", "SNS:Receive", ], - "Resource": "arn:aws:sns:us-east-1:123456789012:test-permissions", - "Condition": {"StringEquals": {"AWS:SourceOwner": "123456789012"}}, + "Resource": "arn:aws:sns:us-east-1:{}:test-permissions".format( + ACCOUNT_ID + ), + "Condition": {"StringEquals": {"AWS:SourceOwner": ACCOUNT_ID}}, }, { "Sid": "test", "Effect": "Allow", "Principal": {"AWS": "arn:aws:iam::999999999999:root"}, "Action": "SNS:Publish", - "Resource": "arn:aws:sns:us-east-1:123456789012:test-permissions", + "Resource": "arn:aws:sns:us-east-1:{}:test-permissions".format( + ACCOUNT_ID + ), }, ], } @@ -308,8 +317,10 @@ def test_add_remove_permissions(): "SNS:Publish", "SNS:Receive", ], - "Resource": "arn:aws:sns:us-east-1:123456789012:test-permissions", - "Condition": {"StringEquals": {"AWS:SourceOwner": "123456789012"}}, + "Resource": "arn:aws:sns:us-east-1:{}:test-permissions".format( + ACCOUNT_ID + ), + "Condition": {"StringEquals": {"AWS:SourceOwner": ACCOUNT_ID}}, } ], } @@ -334,7 +345,7 @@ def test_add_remove_permissions(): ] }, "Action": ["SNS:Publish", "SNS:Subscribe"], - "Resource": "arn:aws:sns:us-east-1:123456789012:test-permissions", + "Resource": "arn:aws:sns:us-east-1:{}:test-permissions".format(ACCOUNT_ID), } ) diff --git a/tests/test_sqs/test_sqs.py b/tests/test_sqs/test_sqs.py index 2af94c5fd..639d6e51c 100644 --- a/tests/test_sqs/test_sqs.py +++ b/tests/test_sqs/test_sqs.py @@ -21,6 +21,7 @@ from moto import mock_sqs, mock_sqs_deprecated, settings from nose import SkipTest from nose.tools import assert_raises from tests.helpers import requires_boto_gte +from moto.core import ACCOUNT_ID @mock_sqs @@ -283,7 +284,7 @@ def test_create_queues_in_multiple_region(): base_url = "https://us-west-1.queue.amazonaws.com" west1_conn.list_queues()["QueueUrls"][0].should.equal( - "{base_url}/123456789012/blah".format(base_url=base_url) + "{base_url}/{AccountId}/blah".format(base_url=base_url, AccountId=ACCOUNT_ID) ) @@ -305,7 +306,9 @@ def test_get_queue_with_prefix(): base_url = "https://us-west-1.queue.amazonaws.com" queue[0].should.equal( - "{base_url}/123456789012/test-queue".format(base_url=base_url) + "{base_url}/{AccountId}/test-queue".format( + base_url=base_url, AccountId=ACCOUNT_ID + ) ) @@ -342,7 +345,7 @@ def test_get_queue_attributes(): response["Attributes"]["MaximumMessageSize"].should.equal("65536") response["Attributes"]["MessageRetentionPeriod"].should.equal("345600") response["Attributes"]["QueueArn"].should.equal( - "arn:aws:sqs:us-east-1:123456789012:test-queue" + "arn:aws:sqs:us-east-1:{}:test-queue".format(ACCOUNT_ID) ) response["Attributes"]["ReceiveMessageWaitTimeSeconds"].should.equal("0") response["Attributes"]["VisibilityTimeout"].should.equal("30") @@ -361,7 +364,7 @@ def test_get_queue_attributes(): { "ApproximateNumberOfMessages": "0", "MaximumMessageSize": "65536", - "QueueArn": "arn:aws:sqs:us-east-1:123456789012:test-queue", + "QueueArn": "arn:aws:sqs:us-east-1:{}:test-queue".format(ACCOUNT_ID), "VisibilityTimeout": "30", } ) @@ -851,7 +854,9 @@ def test_queue_attributes(): attributes = queue.get_attributes() attributes["QueueArn"].should.look_like( - "arn:aws:sqs:us-east-1:123456789012:%s" % queue_name + "arn:aws:sqs:us-east-1:{AccountId}:{name}".format( + AccountId=ACCOUNT_ID, name=queue_name + ) ) attributes["VisibilityTimeout"].should.look_like(str(visibility_timeout)) @@ -1402,7 +1407,7 @@ def test_redrive_policy_available(): def test_redrive_policy_non_existent_queue(): sqs = boto3.client("sqs", region_name="us-east-1") redrive_policy = { - "deadLetterTargetArn": "arn:aws:sqs:us-east-1:123456789012:no-queue", + "deadLetterTargetArn": "arn:aws:sqs:us-east-1:{}:no-queue".format(ACCOUNT_ID), "maxReceiveCount": 1, } diff --git a/tests/test_ssm/test_ssm_boto3.py b/tests/test_ssm/test_ssm_boto3.py index 1b02536a1..5b978520d 100644 --- a/tests/test_ssm/test_ssm_boto3.py +++ b/tests/test_ssm/test_ssm_boto3.py @@ -102,6 +102,18 @@ def test_get_parameters_by_path(): response = client.get_parameters_by_path(Path="/", Recursive=False) len(response["Parameters"]).should.equal(2) {p["Value"] for p in response["Parameters"]}.should.equal(set(["bar", "qux"])) + {p["ARN"] for p in response["Parameters"]}.should.equal( + set( + [ + "arn:aws:ssm:us-east-1:1234567890:parameter/foo", + "arn:aws:ssm:us-east-1:1234567890:parameter/baz", + ] + ) + ) + { + p["LastModifiedDate"].should.be.a(datetime.datetime) + for p in response["Parameters"] + } response = client.get_parameters_by_path(Path="/", Recursive=True) len(response["Parameters"]).should.equal(9) @@ -158,6 +170,20 @@ def test_get_parameters_by_path(): len(response["Parameters"]).should.equal(1) {p["Name"] for p in response["Parameters"]}.should.equal(set(["/baz/pwd"])) + response = client.get_parameters_by_path(Path="/", Recursive=True, MaxResults=4) + len(response["Parameters"]).should.equal(4) + response["NextToken"].should.equal("4") + response = client.get_parameters_by_path( + Path="/", Recursive=True, MaxResults=4, NextToken=response["NextToken"] + ) + len(response["Parameters"]).should.equal(4) + response["NextToken"].should.equal("8") + response = client.get_parameters_by_path( + Path="/", Recursive=True, MaxResults=4, NextToken=response["NextToken"] + ) + len(response["Parameters"]).should.equal(1) + response.should_not.have.key("NextToken") + @mock_ssm def test_put_parameter(): @@ -176,6 +202,11 @@ def test_put_parameter(): response["Parameters"][0]["Value"].should.equal("value") response["Parameters"][0]["Type"].should.equal("String") response["Parameters"][0]["Version"].should.equal(1) + response["Parameters"][0]["LastModifiedDate"].should.be.a(datetime.datetime) + response["Parameters"][0]["ARN"].should.equal( + "arn:aws:ssm:us-east-1:1234567890:parameter/test" + ) + initial_modification_date = response["Parameters"][0]["LastModifiedDate"] try: client.put_parameter( @@ -194,6 +225,12 @@ def test_put_parameter(): response["Parameters"][0]["Value"].should.equal("value") response["Parameters"][0]["Type"].should.equal("String") response["Parameters"][0]["Version"].should.equal(1) + response["Parameters"][0]["LastModifiedDate"].should.equal( + initial_modification_date + ) + response["Parameters"][0]["ARN"].should.equal( + "arn:aws:ssm:us-east-1:1234567890:parameter/test" + ) response = client.put_parameter( Name="test", @@ -213,6 +250,12 @@ def test_put_parameter(): response["Parameters"][0]["Value"].should.equal("value 3") response["Parameters"][0]["Type"].should.equal("String") response["Parameters"][0]["Version"].should.equal(2) + response["Parameters"][0]["LastModifiedDate"].should_not.equal( + initial_modification_date + ) + response["Parameters"][0]["ARN"].should.equal( + "arn:aws:ssm:us-east-1:1234567890:parameter/test" + ) @mock_ssm @@ -239,6 +282,10 @@ def test_get_parameter(): response["Parameter"]["Name"].should.equal("test") response["Parameter"]["Value"].should.equal("value") response["Parameter"]["Type"].should.equal("String") + response["Parameter"]["LastModifiedDate"].should.be.a(datetime.datetime) + response["Parameter"]["ARN"].should.equal( + "arn:aws:ssm:us-east-1:1234567890:parameter/test" + ) @mock_ssm diff --git a/tests/test_stepfunctions/test_stepfunctions.py b/tests/test_stepfunctions/test_stepfunctions.py index 6c391f0d1..3e0a8115d 100644 --- a/tests/test_stepfunctions/test_stepfunctions.py +++ b/tests/test_stepfunctions/test_stepfunctions.py @@ -9,7 +9,7 @@ from botocore.exceptions import ClientError from nose.tools import assert_raises from moto import mock_sts, mock_stepfunctions - +from moto.core import ACCOUNT_ID region = "us-east-1" simple_definition = ( @@ -34,7 +34,7 @@ def test_state_machine_creation_succeeds(): response["ResponseMetadata"]["HTTPStatusCode"].should.equal(200) response["creationDate"].should.be.a(datetime) response["stateMachineArn"].should.equal( - "arn:aws:states:" + region + ":123456789012:stateMachine:" + name + "arn:aws:states:" + region + ":" + ACCOUNT_ID + ":stateMachine:" + name ) @@ -286,7 +286,7 @@ def test_state_machine_can_deleted_nonexisting_machine(): client = boto3.client("stepfunctions", region_name=region) # unknown_state_machine = ( - "arn:aws:states:" + region + ":123456789012:stateMachine:unknown" + "arn:aws:states:" + region + ":" + ACCOUNT_ID + ":stateMachine:unknown" ) response = client.delete_state_machine(stateMachineArn=unknown_state_machine) response["ResponseMetadata"]["HTTPStatusCode"].should.equal(200) diff --git a/tests/test_sts/test_sts.py b/tests/test_sts/test_sts.py index 2cb1c49e7..4dee9184f 100644 --- a/tests/test_sts/test_sts.py +++ b/tests/test_sts/test_sts.py @@ -10,7 +10,7 @@ import sure # noqa from moto import mock_sts, mock_sts_deprecated, mock_iam, settings -from moto.iam.models import ACCOUNT_ID +from moto.core import ACCOUNT_ID from moto.sts.responses import MAX_FEDERATION_TOKEN_POLICY_LENGTH