diff --git a/IMPLEMENTATION_COVERAGE.md b/IMPLEMENTATION_COVERAGE.md index c45abd1ba..63b41d97c 100644 --- a/IMPLEMENTATION_COVERAGE.md +++ b/IMPLEMENTATION_COVERAGE.md @@ -364,7 +364,7 @@ ## appsync
-24% implemented +25% implemented - [ ] associate_api - [ ] associate_merged_graphql_api @@ -394,7 +394,6 @@ - [ ] get_api_association - [ ] get_api_cache - [ ] get_data_source -- [ ] get_data_source_introspection - [ ] get_domain_name - [ ] get_function - [X] get_graphql_api @@ -414,7 +413,6 @@ - [X] list_tags_for_resource - [ ] list_types - [ ] list_types_by_association -- [ ] start_data_source_introspection - [X] start_schema_creation - [ ] start_schema_merge - [X] tag_resource @@ -575,6 +573,89 @@ - [X] update_auto_scaling_group
+## backup +
+11% implemented + +- [ ] cancel_legal_hold +- [X] create_backup_plan +- [ ] create_backup_selection +- [X] create_backup_vault +- [ ] create_framework +- [ ] create_legal_hold +- [ ] create_logically_air_gapped_backup_vault +- [ ] create_report_plan +- [X] delete_backup_plan +- [ ] delete_backup_selection +- [ ] delete_backup_vault +- [ ] delete_backup_vault_access_policy +- [ ] delete_backup_vault_lock_configuration +- [ ] delete_backup_vault_notifications +- [ ] delete_framework +- [ ] delete_recovery_point +- [ ] delete_report_plan +- [ ] describe_backup_job +- [ ] describe_backup_vault +- [ ] describe_copy_job +- [ ] describe_framework +- [ ] describe_global_settings +- [ ] describe_protected_resource +- [ ] describe_recovery_point +- [ ] describe_region_settings +- [ ] describe_report_job +- [ ] describe_report_plan +- [ ] describe_restore_job +- [ ] disassociate_recovery_point +- [ ] disassociate_recovery_point_from_parent +- [ ] export_backup_plan_template +- [X] get_backup_plan +- [ ] get_backup_plan_from_json +- [ ] get_backup_plan_from_template +- [ ] get_backup_selection +- [ ] get_backup_vault_access_policy +- [ ] get_backup_vault_notifications +- [ ] get_legal_hold +- [ ] get_recovery_point_restore_metadata +- [ ] get_supported_resource_types +- [ ] list_backup_job_summaries +- [ ] list_backup_jobs +- [ ] list_backup_plan_templates +- [ ] list_backup_plan_versions +- [X] list_backup_plans +- [ ] list_backup_selections +- [X] list_backup_vaults +- [ ] list_copy_job_summaries +- [ ] list_copy_jobs +- [ ] list_frameworks +- [ ] list_legal_holds +- [ ] list_protected_resources +- [ ] list_protected_resources_by_backup_vault +- [ ] list_recovery_points_by_backup_vault +- [ ] list_recovery_points_by_legal_hold +- [ ] list_recovery_points_by_resource +- [ ] list_report_jobs +- [ ] list_report_plans +- [ ] list_restore_job_summaries +- [ ] list_restore_jobs +- [X] list_tags +- [ ] put_backup_vault_access_policy +- [ ] put_backup_vault_lock_configuration +- [ ] put_backup_vault_notifications +- [ ] start_backup_job +- [ ] start_copy_job +- [ ] start_report_job +- [ ] start_restore_job +- [ ] stop_backup_job +- [X] tag_resource +- [X] untag_resource +- [ ] update_backup_plan +- [ ] update_framework +- [ ] update_global_settings +- [ ] update_recovery_point_lifecycle +- [ ] update_region_settings +- [ ] update_report_plan +
+ ## batch
100% implemented @@ -1101,7 +1182,6 @@ - [ ] update_pull_request_status - [ ] update_pull_request_title - [ ] update_repository_description -- [ ] update_repository_encryption_key - [ ] update_repository_name
@@ -2653,7 +2733,7 @@ ## efs
-54% implemented +56% implemented - [X] create_access_point - [X] create_file_system @@ -2685,24 +2765,20 @@ - [X] tag_resource - [X] untag_resource - [ ] update_file_system -- [ ] update_file_system_protection
## eks
-26% implemented +33% implemented -- [ ] associate_access_policy - [ ] associate_encryption_config - [ ] associate_identity_provider_config -- [ ] create_access_entry - [ ] create_addon - [X] create_cluster - [ ] create_eks_anywhere_subscription - [X] create_fargate_profile - [X] create_nodegroup - [ ] create_pod_identity_association -- [ ] delete_access_entry - [ ] delete_addon - [X] delete_cluster - [ ] delete_eks_anywhere_subscription @@ -2710,7 +2786,6 @@ - [X] delete_nodegroup - [ ] delete_pod_identity_association - [ ] deregister_cluster -- [ ] describe_access_entry - [ ] describe_addon - [ ] describe_addon_configuration - [ ] describe_addon_versions @@ -2718,21 +2793,15 @@ - [ ] describe_eks_anywhere_subscription - [X] describe_fargate_profile - [ ] describe_identity_provider_config -- [ ] describe_insight - [X] describe_nodegroup - [ ] describe_pod_identity_association - [ ] describe_update -- [ ] disassociate_access_policy - [ ] disassociate_identity_provider_config -- [ ] list_access_entries -- [ ] list_access_policies - [ ] list_addons -- [ ] list_associated_access_policies - [X] list_clusters - [ ] list_eks_anywhere_subscriptions - [X] list_fargate_profiles - [ ] list_identity_provider_configs -- [ ] list_insights - [X] list_nodegroups - [ ] list_pod_identity_associations - [X] list_tags_for_resource @@ -2740,7 +2809,6 @@ - [ ] register_cluster - [X] tag_resource - [X] untag_resource -- [ ] update_access_entry - [ ] update_addon - [ ] update_cluster_config - [ ] update_cluster_version @@ -2752,14 +2820,13 @@ ## elasticache
-8% implemented +9% implemented - [ ] add_tags_to_resource - [ ] authorize_cache_security_group_ingress - [ ] batch_apply_update_action - [ ] batch_stop_update_action - [ ] complete_migration -- [ ] copy_serverless_cache_snapshot - [ ] copy_snapshot - [X] create_cache_cluster - [ ] create_cache_parameter_group @@ -2767,8 +2834,6 @@ - [ ] create_cache_subnet_group - [ ] create_global_replication_group - [ ] create_replication_group -- [ ] create_serverless_cache -- [ ] create_serverless_cache_snapshot - [ ] create_snapshot - [X] create_user - [ ] create_user_group @@ -2780,8 +2845,6 @@ - [ ] delete_cache_subnet_group - [ ] delete_global_replication_group - [ ] delete_replication_group -- [ ] delete_serverless_cache -- [ ] delete_serverless_cache_snapshot - [ ] delete_snapshot - [X] delete_user - [ ] delete_user_group @@ -2797,15 +2860,12 @@ - [ ] describe_replication_groups - [ ] describe_reserved_cache_nodes - [ ] describe_reserved_cache_nodes_offerings -- [ ] describe_serverless_cache_snapshots -- [ ] describe_serverless_caches - [ ] describe_service_updates - [ ] describe_snapshots - [ ] describe_update_actions - [ ] describe_user_groups - [X] describe_users - [ ] disassociate_global_replication_group -- [ ] export_serverless_cache_snapshot - [ ] failover_global_replication_group - [ ] increase_node_groups_in_global_replication_group - [ ] increase_replica_count @@ -2817,7 +2877,6 @@ - [ ] modify_global_replication_group - [ ] modify_replication_group - [ ] modify_replication_group_shard_configuration -- [ ] modify_serverless_cache - [ ] modify_user - [ ] modify_user_group - [ ] purchase_reserved_cache_nodes_offering @@ -3043,7 +3102,6 @@ - [ ] remove_managed_scaling_policy - [X] remove_tags - [X] run_job_flow -- [ ] set_keep_job_flow_alive_when_no_steps - [X] set_termination_protection - [X] set_visible_to_all_users - [ ] start_notebook_execution @@ -3663,7 +3721,7 @@ ## guardduty
-15% implemented +16% implemented - [ ] accept_administrator_invitation - [ ] accept_invitation @@ -3703,7 +3761,6 @@ - [ ] get_master_account - [ ] get_member_detectors - [ ] get_members -- [ ] get_organization_statistics - [ ] get_remaining_free_trial_days - [ ] get_threat_intel_set - [ ] get_usage_statistics @@ -3980,7 +4037,7 @@ ## iot
-31% implemented +32% implemented - [ ] accept_certificate_transfer - [ ] add_thing_to_billing_group @@ -4002,7 +4059,6 @@ - [ ] create_authorizer - [ ] create_billing_group - [X] create_certificate_from_csr -- [ ] create_certificate_provider - [ ] create_custom_metric - [ ] create_dimension - [X] create_domain_configuration @@ -4035,7 +4091,6 @@ - [ ] delete_billing_group - [X] delete_ca_certificate - [X] delete_certificate -- [ ] delete_certificate_provider - [ ] delete_custom_metric - [ ] delete_dimension - [X] delete_domain_configuration @@ -4073,7 +4128,6 @@ - [ ] describe_billing_group - [X] describe_ca_certificate - [X] describe_certificate -- [ ] describe_certificate_provider - [ ] describe_custom_metric - [ ] describe_default_authorizer - [ ] describe_detect_mitigation_actions_task @@ -4133,7 +4187,6 @@ - [ ] list_authorizers - [ ] list_billing_groups - [ ] list_ca_certificates -- [ ] list_certificate_providers - [X] list_certificates - [X] list_certificates_by_ca - [ ] list_custom_metrics @@ -4214,7 +4267,6 @@ - [ ] update_billing_group - [X] update_ca_certificate - [X] update_certificate -- [ ] update_certificate_provider - [ ] update_custom_metric - [ ] update_dimension - [X] update_domain_configuration @@ -4630,7 +4682,6 @@ - [X] put_resource_policy - [X] put_retention_policy - [X] put_subscription_filter -- [ ] start_live_tail - [X] start_query - [ ] stop_query - [X] tag_log_group @@ -4677,7 +4728,7 @@ ## mediaconnect
-35% implemented +36% implemented - [ ] add_bridge_outputs - [ ] add_bridge_sources @@ -4694,7 +4745,6 @@ - [ ] deregister_gateway_instance - [ ] describe_bridge - [X] describe_flow -- [ ] describe_flow_source_metadata - [ ] describe_gateway - [ ] describe_gateway_instance - [ ] describe_offering @@ -4981,10 +5031,9 @@ ## opensearch
-16% implemented +17% implemented - [ ] accept_inbound_connection -- [ ] add_data_source - [X] add_tags - [ ] associate_package - [ ] authorize_vpc_endpoint_access @@ -4993,7 +5042,6 @@ - [ ] create_outbound_connection - [ ] create_package - [ ] create_vpc_endpoint -- [ ] delete_data_source - [X] delete_domain - [ ] delete_inbound_connection - [ ] delete_outbound_connection @@ -5016,12 +5064,10 @@ - [ ] describe_vpc_endpoints - [ ] dissociate_package - [X] get_compatible_versions -- [ ] get_data_source - [ ] get_domain_maintenance_status - [ ] get_package_version_history - [ ] get_upgrade_history - [ ] get_upgrade_status -- [ ] list_data_sources - [ ] list_domain_maintenances - [X] list_domain_names - [ ] list_domains_for_package @@ -5039,7 +5085,6 @@ - [ ] revoke_vpc_endpoint_access - [ ] start_domain_maintenance - [ ] start_service_software_update -- [ ] update_data_source - [X] update_domain_config - [ ] update_package - [ ] update_scheduled_action @@ -5592,7 +5637,6 @@ - [ ] update_analysis - [ ] update_analysis_permissions - [ ] update_dashboard -- [ ] update_dashboard_links - [ ] update_dashboard_permissions - [ ] update_dashboard_published_version - [ ] update_data_set @@ -5663,7 +5707,7 @@ ## rds
-35% implemented +37% implemented - [ ] add_role_to_db_cluster - [ ] add_role_to_db_instance @@ -5739,7 +5783,6 @@ - [ ] describe_db_proxy_endpoints - [ ] describe_db_proxy_target_groups - [ ] describe_db_proxy_targets -- [ ] describe_db_recommendations - [ ] describe_db_security_groups - [ ] describe_db_snapshot_attributes - [ ] describe_db_snapshot_tenant_databases @@ -5762,9 +5805,7 @@ - [ ] describe_source_regions - [ ] describe_tenant_databases - [ ] describe_valid_db_instance_modifications -- [ ] disable_http_endpoint - [ ] download_db_log_file_portion -- [ ] enable_http_endpoint - [ ] failover_db_cluster - [ ] failover_global_cluster - [X] list_tags_for_resource @@ -5781,7 +5822,6 @@ - [ ] modify_db_proxy - [ ] modify_db_proxy_endpoint - [ ] modify_db_proxy_target_group -- [ ] modify_db_recommendation - [ ] modify_db_snapshot - [ ] modify_db_snapshot_attribute - [X] modify_db_subnet_group @@ -6323,14 +6363,13 @@ ## s3
-66% implemented +68% implemented - [X] abort_multipart_upload - [X] complete_multipart_upload - [X] copy_object - [X] create_bucket - [X] create_multipart_upload -- [ ] create_session - [X] delete_bucket - [ ] delete_bucket_analytics_configuration - [X] delete_bucket_cors @@ -6386,7 +6425,6 @@ - [ ] list_bucket_inventory_configurations - [ ] list_bucket_metrics_configurations - [X] list_buckets -- [ ] list_directory_buckets - [ ] list_multipart_uploads - [X] list_object_versions - [X] list_objects @@ -6526,7 +6564,7 @@ ## sagemaker
-19% implemented +20% implemented - [ ] add_association - [X] add_tags @@ -6539,7 +6577,6 @@ - [ ] create_artifact - [ ] create_auto_ml_job - [ ] create_auto_ml_job_v2 -- [ ] create_cluster - [ ] create_code_repository - [ ] create_compilation_job - [ ] create_context @@ -6559,7 +6596,6 @@ - [ ] create_hyper_parameter_tuning_job - [ ] create_image - [ ] create_image_version -- [ ] create_inference_component - [ ] create_inference_experiment - [ ] create_inference_recommendations_job - [ ] create_labeling_job @@ -6594,9 +6630,7 @@ - [ ] delete_app_image_config - [ ] delete_artifact - [ ] delete_association -- [ ] delete_cluster - [ ] delete_code_repository -- [ ] delete_compilation_job - [ ] delete_context - [ ] delete_data_quality_job_definition - [ ] delete_device_fleet @@ -6613,7 +6647,6 @@ - [ ] delete_human_task_ui - [ ] delete_image - [ ] delete_image_version -- [ ] delete_inference_component - [ ] delete_inference_experiment - [X] delete_model - [ ] delete_model_bias_job_definition @@ -6644,8 +6677,6 @@ - [ ] describe_artifact - [ ] describe_auto_ml_job - [ ] describe_auto_ml_job_v2 -- [ ] describe_cluster -- [ ] describe_cluster_node - [ ] describe_code_repository - [ ] describe_compilation_job - [ ] describe_context @@ -6667,7 +6698,6 @@ - [ ] describe_hyper_parameter_tuning_job - [ ] describe_image - [ ] describe_image_version -- [ ] describe_inference_component - [ ] describe_inference_experiment - [ ] describe_inference_recommendations_job - [ ] describe_labeling_job @@ -6717,8 +6747,6 @@ - [ ] list_associations - [ ] list_auto_ml_jobs - [ ] list_candidates_for_auto_ml_job -- [ ] list_cluster_nodes -- [ ] list_clusters - [ ] list_code_repositories - [ ] list_compilation_jobs - [ ] list_contexts @@ -6740,7 +6768,6 @@ - [ ] list_hyper_parameter_tuning_jobs - [ ] list_image_versions - [ ] list_images -- [ ] list_inference_components - [ ] list_inference_experiments - [ ] list_inference_recommendations_job_steps - [ ] list_inference_recommendations_jobs @@ -6813,7 +6840,6 @@ - [ ] update_action - [ ] update_app_image_config - [ ] update_artifact -- [ ] update_cluster - [ ] update_code_repository - [ ] update_context - [ ] update_device_fleet @@ -6827,8 +6853,6 @@ - [ ] update_hub - [ ] update_image - [ ] update_image_version -- [ ] update_inference_component -- [ ] update_inference_component_runtime_config - [ ] update_inference_experiment - [ ] update_model_card - [X] update_model_package @@ -7658,7 +7682,7 @@ ## transcribe
-37% implemented +41% implemented - [ ] create_call_analytics_category - [ ] create_language_model @@ -7668,7 +7692,6 @@ - [ ] delete_call_analytics_category - [ ] delete_call_analytics_job - [ ] delete_language_model -- [ ] delete_medical_scribe_job - [X] delete_medical_transcription_job - [X] delete_medical_vocabulary - [X] delete_transcription_job @@ -7677,7 +7700,6 @@ - [ ] describe_language_model - [ ] get_call_analytics_category - [ ] get_call_analytics_job -- [ ] get_medical_scribe_job - [X] get_medical_transcription_job - [X] get_medical_vocabulary - [X] get_transcription_job @@ -7686,7 +7708,6 @@ - [ ] list_call_analytics_categories - [ ] list_call_analytics_jobs - [ ] list_language_models -- [ ] list_medical_scribe_jobs - [X] list_medical_transcription_jobs - [X] list_medical_vocabularies - [ ] list_tags_for_resource @@ -7694,7 +7715,6 @@ - [X] list_vocabularies - [ ] list_vocabulary_filters - [ ] start_call_analytics_job -- [ ] start_medical_scribe_job - [X] start_medical_transcription_job - [X] start_transcription_job - [ ] tag_resource @@ -7785,14 +7805,10 @@ - arc-zonal-shift - auditmanager - autoscaling-plans -- b2bi -- backup - backup-gateway - backupstorage - bcm-data-exports - bedrock -- bedrock-agent -- bedrock-agent-runtime - bedrock-runtime - billingconductor - braket @@ -7803,7 +7819,6 @@ - chime-sdk-messaging - chime-sdk-voice - cleanrooms -- cleanroomsml - cloud9 - cloudcontrol - clouddirectory @@ -7915,9 +7930,7 @@ - machinelearning - macie2 - managedblockchain-query -- marketplace-agreement - marketplace-catalog -- marketplace-deployment - marketplace-entitlement - marketplacecommerceanalytics - mediaconvert @@ -7935,11 +7948,9 @@ - mobile - mturk - mwaa -- neptune-graph - neptunedata - network-firewall - networkmanager -- networkmonitor - nimble - oam - omics @@ -7960,8 +7971,6 @@ - pricing - privatenetworks - proton -- qbusiness -- qconnect - qldb - qldb-session - rbin diff --git a/docs/docs/services/appsync.rst b/docs/docs/services/appsync.rst index 83d0540c7..bd2e16dd2 100644 --- a/docs/docs/services/appsync.rst +++ b/docs/docs/services/appsync.rst @@ -55,7 +55,6 @@ appsync - [ ] get_api_association - [ ] get_api_cache - [ ] get_data_source -- [ ] get_data_source_introspection - [ ] get_domain_name - [ ] get_function - [X] get_graphql_api @@ -83,7 +82,6 @@ appsync - [X] list_tags_for_resource - [ ] list_types - [ ] list_types_by_association -- [ ] start_data_source_introspection - [X] start_schema_creation - [ ] start_schema_merge - [X] tag_resource diff --git a/docs/docs/services/backup.rst b/docs/docs/services/backup.rst new file mode 100644 index 000000000..108d2c522 --- /dev/null +++ b/docs/docs/services/backup.rst @@ -0,0 +1,119 @@ +.. _implementedservice_backup: + +.. |start-h3| raw:: html + +

+ +.. |end-h3| raw:: html + +

+ +====== +backup +====== + +.. autoclass:: moto.backup.models.BackupBackend + +|start-h3| Example usage |end-h3| + +.. sourcecode:: python + + @mock_backup + def test_backup_behaviour: + boto3.client("backup") + ... + + + +|start-h3| Implemented features for this service |end-h3| + +- [ ] cancel_legal_hold +- [X] create_backup_plan +- [ ] create_backup_selection +- [X] create_backup_vault +- [ ] create_framework +- [ ] create_legal_hold +- [ ] create_logically_air_gapped_backup_vault +- [ ] create_report_plan +- [X] delete_backup_plan +- [ ] delete_backup_selection +- [ ] delete_backup_vault +- [ ] delete_backup_vault_access_policy +- [ ] delete_backup_vault_lock_configuration +- [ ] delete_backup_vault_notifications +- [ ] delete_framework +- [ ] delete_recovery_point +- [ ] delete_report_plan +- [ ] describe_backup_job +- [ ] describe_backup_vault +- [ ] describe_copy_job +- [ ] describe_framework +- [ ] describe_global_settings +- [ ] describe_protected_resource +- [ ] describe_recovery_point +- [ ] describe_region_settings +- [ ] describe_report_job +- [ ] describe_report_plan +- [ ] describe_restore_job +- [ ] disassociate_recovery_point +- [ ] disassociate_recovery_point_from_parent +- [ ] export_backup_plan_template +- [X] get_backup_plan +- [ ] get_backup_plan_from_json +- [ ] get_backup_plan_from_template +- [ ] get_backup_selection +- [ ] get_backup_vault_access_policy +- [ ] get_backup_vault_notifications +- [ ] get_legal_hold +- [ ] get_recovery_point_restore_metadata +- [ ] get_supported_resource_types +- [ ] list_backup_job_summaries +- [ ] list_backup_jobs +- [ ] list_backup_plan_templates +- [ ] list_backup_plan_versions +- [X] list_backup_plans + + Pagination is not yet implemented + + +- [ ] list_backup_selections +- [X] list_backup_vaults + + Pagination is not yet implemented + + +- [ ] list_copy_job_summaries +- [ ] list_copy_jobs +- [ ] list_frameworks +- [ ] list_legal_holds +- [ ] list_protected_resources +- [ ] list_protected_resources_by_backup_vault +- [ ] list_recovery_points_by_backup_vault +- [ ] list_recovery_points_by_legal_hold +- [ ] list_recovery_points_by_resource +- [ ] list_report_jobs +- [ ] list_report_plans +- [ ] list_restore_job_summaries +- [ ] list_restore_jobs +- [X] list_tags + + Pagination is not yet implemented + + +- [ ] put_backup_vault_access_policy +- [ ] put_backup_vault_lock_configuration +- [ ] put_backup_vault_notifications +- [ ] start_backup_job +- [ ] start_copy_job +- [ ] start_report_job +- [ ] start_restore_job +- [ ] stop_backup_job +- [X] tag_resource +- [X] untag_resource +- [ ] update_backup_plan +- [ ] update_framework +- [ ] update_global_settings +- [ ] update_recovery_point_lifecycle +- [ ] update_region_settings +- [ ] update_report_plan + diff --git a/docs/docs/services/codecommit.rst b/docs/docs/services/codecommit.rst index dd09e58fd..47368ba04 100644 --- a/docs/docs/services/codecommit.rst +++ b/docs/docs/services/codecommit.rst @@ -102,6 +102,5 @@ codecommit - [ ] update_pull_request_status - [ ] update_pull_request_title - [ ] update_repository_description -- [ ] update_repository_encryption_key - [ ] update_repository_name diff --git a/docs/docs/services/efs.rst b/docs/docs/services/efs.rst index bad8c2223..47d76e667 100644 --- a/docs/docs/services/efs.rst +++ b/docs/docs/services/efs.rst @@ -98,5 +98,4 @@ efs - [X] tag_resource - [X] untag_resource - [ ] update_file_system -- [ ] update_file_system_protection diff --git a/docs/docs/services/eks.rst b/docs/docs/services/eks.rst index ecb31c1bb..ac7c261be 100644 --- a/docs/docs/services/eks.rst +++ b/docs/docs/services/eks.rst @@ -25,17 +25,14 @@ eks |start-h3| Implemented features for this service |end-h3| -- [ ] associate_access_policy - [ ] associate_encryption_config - [ ] associate_identity_provider_config -- [ ] create_access_entry - [ ] create_addon - [X] create_cluster - [ ] create_eks_anywhere_subscription - [X] create_fargate_profile - [X] create_nodegroup - [ ] create_pod_identity_association -- [ ] delete_access_entry - [ ] delete_addon - [X] delete_cluster - [ ] delete_eks_anywhere_subscription @@ -43,7 +40,6 @@ eks - [X] delete_nodegroup - [ ] delete_pod_identity_association - [ ] deregister_cluster -- [ ] describe_access_entry - [ ] describe_addon - [ ] describe_addon_configuration - [ ] describe_addon_versions @@ -51,21 +47,15 @@ eks - [ ] describe_eks_anywhere_subscription - [X] describe_fargate_profile - [ ] describe_identity_provider_config -- [ ] describe_insight - [X] describe_nodegroup - [ ] describe_pod_identity_association - [ ] describe_update -- [ ] disassociate_access_policy - [ ] disassociate_identity_provider_config -- [ ] list_access_entries -- [ ] list_access_policies - [ ] list_addons -- [ ] list_associated_access_policies - [X] list_clusters - [ ] list_eks_anywhere_subscriptions - [X] list_fargate_profiles - [ ] list_identity_provider_configs -- [ ] list_insights - [X] list_nodegroups - [ ] list_pod_identity_associations - [X] list_tags_for_resource @@ -85,7 +75,6 @@ eks This function currently will remove tags on an EKS cluster only. It does not remove tags from a managed node group -- [ ] update_access_entry - [ ] update_addon - [ ] update_cluster_config - [ ] update_cluster_version diff --git a/docs/docs/services/elasticache.rst b/docs/docs/services/elasticache.rst index ff44a3044..227bf1204 100644 --- a/docs/docs/services/elasticache.rst +++ b/docs/docs/services/elasticache.rst @@ -32,7 +32,6 @@ elasticache - [ ] batch_apply_update_action - [ ] batch_stop_update_action - [ ] complete_migration -- [ ] copy_serverless_cache_snapshot - [ ] copy_snapshot - [X] create_cache_cluster - [ ] create_cache_parameter_group @@ -40,8 +39,6 @@ elasticache - [ ] create_cache_subnet_group - [ ] create_global_replication_group - [ ] create_replication_group -- [ ] create_serverless_cache -- [ ] create_serverless_cache_snapshot - [ ] create_snapshot - [X] create_user - [ ] create_user_group @@ -53,8 +50,6 @@ elasticache - [ ] delete_cache_subnet_group - [ ] delete_global_replication_group - [ ] delete_replication_group -- [ ] delete_serverless_cache -- [ ] delete_serverless_cache_snapshot - [ ] delete_snapshot - [X] delete_user - [ ] delete_user_group @@ -70,8 +65,6 @@ elasticache - [ ] describe_replication_groups - [ ] describe_reserved_cache_nodes - [ ] describe_reserved_cache_nodes_offerings -- [ ] describe_serverless_cache_snapshots -- [ ] describe_serverless_caches - [ ] describe_service_updates - [ ] describe_snapshots - [ ] describe_update_actions @@ -83,7 +76,6 @@ elasticache - [ ] disassociate_global_replication_group -- [ ] export_serverless_cache_snapshot - [ ] failover_global_replication_group - [ ] increase_node_groups_in_global_replication_group - [ ] increase_replica_count @@ -95,7 +87,6 @@ elasticache - [ ] modify_global_replication_group - [ ] modify_replication_group - [ ] modify_replication_group_shard_configuration -- [ ] modify_serverless_cache - [ ] modify_user - [ ] modify_user_group - [ ] purchase_reserved_cache_nodes_offering diff --git a/docs/docs/services/emr.rst b/docs/docs/services/emr.rst index 595fb150c..4a1d8fc06 100644 --- a/docs/docs/services/emr.rst +++ b/docs/docs/services/emr.rst @@ -72,7 +72,6 @@ emr - [ ] remove_managed_scaling_policy - [X] remove_tags - [X] run_job_flow -- [ ] set_keep_job_flow_alive_when_no_steps - [X] set_termination_protection - [X] set_visible_to_all_users - [ ] start_notebook_execution diff --git a/docs/docs/services/guardduty.rst b/docs/docs/services/guardduty.rst index c339fb57c..4dbcdd873 100644 --- a/docs/docs/services/guardduty.rst +++ b/docs/docs/services/guardduty.rst @@ -63,7 +63,6 @@ guardduty - [ ] get_master_account - [ ] get_member_detectors - [ ] get_members -- [ ] get_organization_statistics - [ ] get_remaining_free_trial_days - [ ] get_threat_intel_set - [ ] get_usage_statistics diff --git a/docs/docs/services/iot.rst b/docs/docs/services/iot.rst index 5b9c1c847..b52b080f4 100644 --- a/docs/docs/services/iot.rst +++ b/docs/docs/services/iot.rst @@ -49,7 +49,6 @@ iot - [ ] create_authorizer - [ ] create_billing_group - [X] create_certificate_from_csr -- [ ] create_certificate_provider - [ ] create_custom_metric - [ ] create_dimension - [X] create_domain_configuration @@ -86,7 +85,6 @@ iot - [ ] delete_billing_group - [X] delete_ca_certificate - [X] delete_certificate -- [ ] delete_certificate_provider - [ ] delete_custom_metric - [ ] delete_dimension - [X] delete_domain_configuration @@ -132,7 +130,6 @@ iot - [ ] describe_billing_group - [X] describe_ca_certificate - [X] describe_certificate -- [ ] describe_certificate_provider - [ ] describe_custom_metric - [ ] describe_default_authorizer - [ ] describe_detect_mitigation_actions_task @@ -196,7 +193,6 @@ iot - [ ] list_authorizers - [ ] list_billing_groups - [ ] list_ca_certificates -- [ ] list_certificate_providers - [X] list_certificates Pagination is not yet implemented @@ -325,7 +321,6 @@ iot - [X] update_certificate -- [ ] update_certificate_provider - [ ] update_custom_metric - [ ] update_dimension - [X] update_domain_configuration diff --git a/docs/docs/services/logs.rst b/docs/docs/services/logs.rst index 395c1e82d..cfb7f4bfd 100644 --- a/docs/docs/services/logs.rst +++ b/docs/docs/services/logs.rst @@ -126,7 +126,6 @@ logs - [X] put_retention_policy - [X] put_subscription_filter -- [ ] start_live_tail - [X] start_query - [ ] stop_query - [X] tag_log_group diff --git a/docs/docs/services/mediaconnect.rst b/docs/docs/services/mediaconnect.rst index 4c415253d..08fc5177e 100644 --- a/docs/docs/services/mediaconnect.rst +++ b/docs/docs/services/mediaconnect.rst @@ -40,7 +40,6 @@ mediaconnect - [ ] deregister_gateway_instance - [ ] describe_bridge - [X] describe_flow -- [ ] describe_flow_source_metadata - [ ] describe_gateway - [ ] describe_gateway_instance - [ ] describe_offering diff --git a/docs/docs/services/opensearch.rst b/docs/docs/services/opensearch.rst index 7402bf828..6a608a250 100644 --- a/docs/docs/services/opensearch.rst +++ b/docs/docs/services/opensearch.rst @@ -28,7 +28,6 @@ opensearch |start-h3| Implemented features for this service |end-h3| - [ ] accept_inbound_connection -- [ ] add_data_source - [X] add_tags - [ ] associate_package - [ ] authorize_vpc_endpoint_access @@ -37,7 +36,6 @@ opensearch - [ ] create_outbound_connection - [ ] create_package - [ ] create_vpc_endpoint -- [ ] delete_data_source - [X] delete_domain - [ ] delete_inbound_connection - [ ] delete_outbound_connection @@ -60,12 +58,10 @@ opensearch - [ ] describe_vpc_endpoints - [ ] dissociate_package - [X] get_compatible_versions -- [ ] get_data_source - [ ] get_domain_maintenance_status - [ ] get_package_version_history - [ ] get_upgrade_history - [ ] get_upgrade_status -- [ ] list_data_sources - [ ] list_domain_maintenances - [X] list_domain_names - [ ] list_domains_for_package @@ -83,7 +79,6 @@ opensearch - [ ] revoke_vpc_endpoint_access - [ ] start_domain_maintenance - [ ] start_service_software_update -- [ ] update_data_source - [X] update_domain_config - [ ] update_package - [ ] update_scheduled_action diff --git a/docs/docs/services/quicksight.rst b/docs/docs/services/quicksight.rst index 0690940e3..848a1bbfe 100644 --- a/docs/docs/services/quicksight.rst +++ b/docs/docs/services/quicksight.rst @@ -188,7 +188,6 @@ quicksight - [ ] update_analysis - [ ] update_analysis_permissions - [ ] update_dashboard -- [ ] update_dashboard_links - [ ] update_dashboard_permissions - [ ] update_dashboard_published_version - [ ] update_data_set diff --git a/docs/docs/services/rds.rst b/docs/docs/services/rds.rst index 6542a8319..ac345da2b 100644 --- a/docs/docs/services/rds.rst +++ b/docs/docs/services/rds.rst @@ -99,7 +99,6 @@ rds - [ ] describe_db_proxy_endpoints - [ ] describe_db_proxy_target_groups - [ ] describe_db_proxy_targets -- [ ] describe_db_recommendations - [ ] describe_db_security_groups - [ ] describe_db_snapshot_attributes - [ ] describe_db_snapshot_tenant_databases @@ -126,9 +125,7 @@ rds - [ ] describe_source_regions - [ ] describe_tenant_databases - [ ] describe_valid_db_instance_modifications -- [ ] disable_http_endpoint - [ ] download_db_log_file_portion -- [ ] enable_http_endpoint - [ ] failover_db_cluster - [ ] failover_global_cluster - [X] list_tags_for_resource @@ -145,7 +142,6 @@ rds - [ ] modify_db_proxy - [ ] modify_db_proxy_endpoint - [ ] modify_db_proxy_target_group -- [ ] modify_db_recommendation - [ ] modify_db_snapshot - [ ] modify_db_snapshot_attribute - [X] modify_db_subnet_group @@ -171,7 +167,7 @@ rds - [ ] restore_db_cluster_to_point_in_time - [X] restore_db_instance_from_db_snapshot - [ ] restore_db_instance_from_s3 -- [ ] restore_db_instance_to_point_in_time +- [X] restore_db_instance_to_point_in_time - [ ] revoke_db_security_group_ingress - [ ] start_activity_stream - [X] start_db_cluster diff --git a/docs/docs/services/s3.rst b/docs/docs/services/s3.rst index 88eef2c87..969b9f7c2 100644 --- a/docs/docs/services/s3.rst +++ b/docs/docs/services/s3.rst @@ -32,7 +32,6 @@ s3 - [X] copy_object - [X] create_bucket - [X] create_multipart_upload -- [ ] create_session - [X] delete_bucket - [ ] delete_bucket_analytics_configuration - [X] delete_bucket_cors @@ -92,7 +91,6 @@ s3 - [ ] list_bucket_inventory_configurations - [ ] list_bucket_metrics_configurations - [X] list_buckets -- [ ] list_directory_buckets - [ ] list_multipart_uploads - [X] list_object_versions - [X] list_objects diff --git a/docs/docs/services/sagemaker.rst b/docs/docs/services/sagemaker.rst index 87b2d2827..52d099cea 100644 --- a/docs/docs/services/sagemaker.rst +++ b/docs/docs/services/sagemaker.rst @@ -36,7 +36,6 @@ sagemaker - [ ] create_artifact - [ ] create_auto_ml_job - [ ] create_auto_ml_job_v2 -- [ ] create_cluster - [ ] create_code_repository - [ ] create_compilation_job - [ ] create_context @@ -56,7 +55,6 @@ sagemaker - [ ] create_hyper_parameter_tuning_job - [ ] create_image - [ ] create_image_version -- [ ] create_inference_component - [ ] create_inference_experiment - [ ] create_inference_recommendations_job - [ ] create_labeling_job @@ -91,9 +89,7 @@ sagemaker - [ ] delete_app_image_config - [ ] delete_artifact - [ ] delete_association -- [ ] delete_cluster - [ ] delete_code_repository -- [ ] delete_compilation_job - [ ] delete_context - [ ] delete_data_quality_job_definition - [ ] delete_device_fleet @@ -110,7 +106,6 @@ sagemaker - [ ] delete_human_task_ui - [ ] delete_image - [ ] delete_image_version -- [ ] delete_inference_component - [ ] delete_inference_experiment - [X] delete_model - [ ] delete_model_bias_job_definition @@ -141,8 +136,6 @@ sagemaker - [ ] describe_artifact - [ ] describe_auto_ml_job - [ ] describe_auto_ml_job_v2 -- [ ] describe_cluster -- [ ] describe_cluster_node - [ ] describe_code_repository - [ ] describe_compilation_job - [ ] describe_context @@ -164,7 +157,6 @@ sagemaker - [ ] describe_hyper_parameter_tuning_job - [ ] describe_image - [ ] describe_image_version -- [ ] describe_inference_component - [ ] describe_inference_experiment - [ ] describe_inference_recommendations_job - [ ] describe_labeling_job @@ -214,8 +206,6 @@ sagemaker - [ ] list_associations - [ ] list_auto_ml_jobs - [ ] list_candidates_for_auto_ml_job -- [ ] list_cluster_nodes -- [ ] list_clusters - [ ] list_code_repositories - [ ] list_compilation_jobs - [ ] list_contexts @@ -237,7 +227,6 @@ sagemaker - [ ] list_hyper_parameter_tuning_jobs - [ ] list_image_versions - [ ] list_images -- [ ] list_inference_components - [ ] list_inference_experiments - [ ] list_inference_recommendations_job_steps - [ ] list_inference_recommendations_jobs @@ -315,7 +304,6 @@ sagemaker - [ ] update_action - [ ] update_app_image_config - [ ] update_artifact -- [ ] update_cluster - [ ] update_code_repository - [ ] update_context - [ ] update_device_fleet @@ -329,8 +317,6 @@ sagemaker - [ ] update_hub - [ ] update_image - [ ] update_image_version -- [ ] update_inference_component -- [ ] update_inference_component_runtime_config - [ ] update_inference_experiment - [ ] update_model_card - [X] update_model_package diff --git a/docs/docs/services/transcribe.rst b/docs/docs/services/transcribe.rst index 4d3536203..df61d80c0 100644 --- a/docs/docs/services/transcribe.rst +++ b/docs/docs/services/transcribe.rst @@ -33,7 +33,6 @@ transcribe - [ ] delete_call_analytics_category - [ ] delete_call_analytics_job - [ ] delete_language_model -- [ ] delete_medical_scribe_job - [X] delete_medical_transcription_job - [X] delete_medical_vocabulary - [X] delete_transcription_job @@ -42,7 +41,6 @@ transcribe - [ ] describe_language_model - [ ] get_call_analytics_category - [ ] get_call_analytics_job -- [ ] get_medical_scribe_job - [X] get_medical_transcription_job - [X] get_medical_vocabulary - [X] get_transcription_job @@ -51,7 +49,6 @@ transcribe - [ ] list_call_analytics_categories - [ ] list_call_analytics_jobs - [ ] list_language_models -- [ ] list_medical_scribe_jobs - [X] list_medical_transcription_jobs - [X] list_medical_vocabularies - [ ] list_tags_for_resource @@ -59,7 +56,6 @@ transcribe - [X] list_vocabularies - [ ] list_vocabulary_filters - [ ] start_call_analytics_job -- [ ] start_medical_scribe_job - [X] start_medical_transcription_job - [X] start_transcription_job - [ ] tag_resource diff --git a/moto/__init__.py b/moto/__init__.py index 72865ad29..7c1b02e69 100644 --- a/moto/__init__.py +++ b/moto/__init__.py @@ -235,6 +235,7 @@ XRaySegment = load_xray_segment() mock_xray = lazy_load(".xray", "mock_xray") mock_xray_client = lazy_load(".xray", "mock_xray_client") mock_wafv2 = lazy_load(".wafv2", "mock_wafv2") +mock_backup = lazy_load(".backup", "mock_backup", boto3_name="backup") class MockAll(ContextDecorator): diff --git a/moto/backend_index.py b/moto/backend_index.py index 300784891..91c8e707a 100644 --- a/moto/backend_index.py +++ b/moto/backend_index.py @@ -1,4 +1,4 @@ -# autogenerated by moto/scripts/update_backend_index.py +# autogenerated by /Users/oxh252/workspace/moto_contribution/moto/scripts/update_backend_index.py import re backend_url_patterns = [ @@ -18,6 +18,7 @@ backend_url_patterns = [ ("appsync", re.compile("https?://appsync\\.(.+)\\.amazonaws\\.com")), ("athena", re.compile("https?://athena\\.(.+)\\.amazonaws\\.com")), ("autoscaling", re.compile("https?://autoscaling\\.(.+)\\.amazonaws\\.com")), + ("backup", re.compile("https?://backup\\.(.+)\\.amazonaws\\.com")), ("batch", re.compile("https?://batch\\.(.+)\\.amazonaws.com")), ("budgets", re.compile("https?://budgets\\.amazonaws\\.com")), ("ce", re.compile("https?://ce\\.(.+)\\.amazonaws\\.com")), diff --git a/moto/backup/__init__.py b/moto/backup/__init__.py new file mode 100644 index 000000000..092556a72 --- /dev/null +++ b/moto/backup/__init__.py @@ -0,0 +1,5 @@ +"""backup module initialization; sets value for base decorator.""" +from ..core.models import base_decorator +from .models import backup_backends + +mock_backup = base_decorator(backup_backends) diff --git a/moto/backup/exceptions.py b/moto/backup/exceptions.py new file mode 100644 index 000000000..dc0e1ad12 --- /dev/null +++ b/moto/backup/exceptions.py @@ -0,0 +1,16 @@ +"""Exceptions raised by the backup service.""" +from moto.core.exceptions import JsonRESTError + + +class BackupClientError(JsonRESTError): + code = 400 + + +class AlreadyExistsException(BackupClientError): + def __init__(self, msg: str): + super().__init__("AlreadyExistsException", f"{msg}") + + +class ResourceNotFoundException(JsonRESTError): + def __init__(self, msg: str): + super().__init__("ResourceNotFoundException", f"{msg}") diff --git a/moto/backup/models.py b/moto/backup/models.py new file mode 100644 index 000000000..f2f040aed --- /dev/null +++ b/moto/backup/models.py @@ -0,0 +1,236 @@ +"""BackupBackend class with methods for supported APIs.""" +from copy import deepcopy +from typing import Any, Dict, List, Optional, Tuple + +from moto.core import BackendDict, BaseBackend, BaseModel +from moto.core.utils import unix_time +from moto.moto_api._internal import mock_random +from moto.utilities.tagging_service import TaggingService + +from .exceptions import AlreadyExistsException, ResourceNotFoundException + + +class Plan(BaseModel): + def __init__( + self, + backup_plan: Dict[str, Any], + creator_request_id: str, + backend: "BackupBackend", + ): + + self.backup_plan_id = str(mock_random.uuid4()) + self.backup_plan_arn = f"arn:aws:backup:{backend.region_name}:{backend.account_id}:backup-plan:{self.backup_plan_id}" + self.creation_date = unix_time() + ran_str = mock_random.get_random_string(length=48) + self.version_id = ran_str + self.creator_request_id = creator_request_id + self.backup_plan = backup_plan + adv_settings = backup_plan.get("AdvancedBackupSettings") + self.advanced_backup_settings = adv_settings or [] + self.deletion_date: Optional[float] = None + # Deletion Date is updated when the backup_plan is deleted + self.last_execution_date = None # start_restore_job not yet supported + rules = backup_plan["Rules"] + for rule in rules: + rule["ScheduleExpression"] = rule.get( + "ScheduleExpression", "cron(0 5 ? * * *)" + ) # Default CRON expression in UTC + rule["StartWindowMinutes"] = rule.get( + "StartWindowMinutes", 480 + ) # Default=480 + rule["CompletionWindowMinutes"] = rule.get( + "CompletionWindowMinutes", 10080 + ) # Default=10080 + rule["ScheduleExpressionTimezone"] = rule.get( + "ScheduleExpressionTimezone", "Etc/UTC" + ) # set to Etc/UTc by default + rule["RuleId"] = str(mock_random.uuid4()) + + def to_dict(self) -> Dict[str, Any]: + dct = { + "BackupPlanId": self.backup_plan_id, + "BackupPlanArn": self.backup_plan_arn, + "CreationDate": self.creation_date, + "VersionId": self.version_id, + "AdvancedBackupSettings": self.advanced_backup_settings, + } + return {k: v for k, v in dct.items() if v} + + def to_get_dict(self) -> Dict[str, Any]: + dct = self.to_dict() + dct_options = { + "BackupPlan": self.backup_plan, + "CreatorRequestId": self.creator_request_id, + "DeletionDate": self.deletion_date, + "LastExecutionDate": self.last_execution_date, + } + for key, value in dct_options.items(): + if value is not None: + dct[key] = value + return dct + + def to_list_dict(self) -> Dict[str, Any]: + dct = self.to_get_dict() + dct.pop("BackupPlan") + dct["BackupPlanName"] = self.backup_plan.get("BackupPlanName") + return dct + + +class Vault(BaseModel): + def __init__( + self, + backup_vault_name: str, + encryption_key_arn: str, + creator_request_id: str, + backend: "BackupBackend", + ): + self.backup_vault_name = backup_vault_name + self.backup_vault_arn = f"arn:aws:backup:{backend.region_name}:{backend.account_id}:backup-vault:{backup_vault_name}" + self.creation_date = unix_time() + self.encryption_key_arn = encryption_key_arn + self.creator_request_id = creator_request_id + self.num_of_recovery_points = 0 # start_backup_job not yet supported + self.locked = False # put_backup_vault_lock_configuration + self.min_retention_days = 0 # put_backup_vault_lock_configuration + self.max_retention_days = 0 # put_backup_vault_lock_configuration + self.lock_date = None # put_backup_vault_lock_configuration + + def to_dict(self) -> Dict[str, Any]: + dct = { + "BackupVaultName": self.backup_vault_name, + "BackupVaultArn": self.backup_vault_arn, + "CreationDate": self.creation_date, + } + return dct + + def to_list_dict(self) -> Dict[str, Any]: + dct = self.to_dict() + dct_options: Dict[str, Any] = dict() + dct_options = { + "EncryptionKeyArn": self.encryption_key_arn, + "CreatorRequestId": self.creator_request_id, + "NumberOfRecoveryPoints": self.num_of_recovery_points, + "Locked": self.locked, + "MinRetentionDays": self.min_retention_days, + "MaxRetentionDays": self.max_retention_days, + "LockDate": self.lock_date, + } + for key, value in dct_options.items(): + if value is not None: + dct[key] = value + return dct + + +class BackupBackend(BaseBackend): + """Implementation of Backup APIs.""" + + def __init__(self, region_name: str, account_id: str): + super().__init__(region_name, account_id) + + self.vaults: Dict[str, Vault] = dict() + self.plans: Dict[str, Plan] = dict() + self.tagger = TaggingService() + + def create_backup_plan( + self, + backup_plan: Dict[str, Any], + backup_plan_tags: Dict[str, str], + creator_request_id: str, + ) -> Plan: + + if backup_plan["BackupPlanName"] in list( + p.backup_plan["BackupPlanName"] for p in list(self.plans.values()) + ): + raise AlreadyExistsException( + msg="Backup plan with the same plan document already exists" + ) + plan = Plan( + backup_plan=backup_plan, + creator_request_id=creator_request_id, + backend=self, + ) + if backup_plan_tags: + self.tag_resource(plan.backup_plan_arn, backup_plan_tags) + self.plans[plan.backup_plan_id] = plan + return plan + + def get_backup_plan(self, backup_plan_id: str, version_id: Optional[Any]) -> Plan: + msg = "Failed reading Backup plan with provided version" + if backup_plan_id not in self.plans: + raise ResourceNotFoundException(msg=msg) + plan = self.plans[backup_plan_id] + if version_id: + if plan.version_id == version_id: + return plan + else: + raise ResourceNotFoundException(msg=msg) + return plan + + def delete_backup_plan(self, backup_plan_id: str) -> Tuple[str, str, float, str]: + if backup_plan_id not in self.plans: + raise ResourceNotFoundException( + msg="Failed reading Backup plan with provided version" + ) + deletion_date = unix_time() + res = self.plans[backup_plan_id] + res.deletion_date = deletion_date + return res.backup_plan_id, res.backup_plan_arn, deletion_date, res.version_id + + def list_backup_plans(self, include_deleted: Any) -> List[Plan]: + """ + Pagination is not yet implemented + """ + plans_list = deepcopy(self.plans) + + for plan in list(plans_list.values()): + backup_plan_id = plan.backup_plan_id + if plan.deletion_date is not None: + plans_list.pop(backup_plan_id) + if include_deleted: + return list(self.plans.values()) + return list(plans_list.values()) + + def create_backup_vault( + self, + backup_vault_name: str, + backup_vault_tags: Dict[str, str], + encryption_key_arn: str, + creator_request_id: str, + ) -> Vault: + + if backup_vault_name in self.vaults: + raise AlreadyExistsException( + msg="Backup vault with the same name already exists" + ) + vault = Vault( + backup_vault_name=backup_vault_name, + encryption_key_arn=encryption_key_arn, + creator_request_id=creator_request_id, + backend=self, + ) + if backup_vault_tags: + self.tag_resource(vault.backup_vault_arn, backup_vault_tags) + self.vaults[backup_vault_name] = vault + return vault + + def list_backup_vaults(self) -> List[Vault]: + """ + Pagination is not yet implemented + """ + return list(self.vaults.values()) + + def list_tags(self, resource_arn: str) -> Dict[str, str]: + """ + Pagination is not yet implemented + """ + return self.tagger.get_tag_dict_for_resource(resource_arn) + + def tag_resource(self, resource_arn: str, tags: Dict[str, str]) -> None: + tags_input = TaggingService.convert_dict_to_tags_input(tags or {}) + self.tagger.tag_resource(resource_arn, tags_input) + + def untag_resource(self, resource_arn: str, tag_key_list: List[str]) -> None: + self.tagger.untag_resource_using_names(resource_arn, tag_key_list) + + +backup_backends = BackendDict(BackupBackend, "backup") diff --git a/moto/backup/responses.py b/moto/backup/responses.py new file mode 100644 index 000000000..42483740b --- /dev/null +++ b/moto/backup/responses.py @@ -0,0 +1,116 @@ +"""Handles incoming backup requests, invokes methods, returns responses.""" +import json +from urllib.parse import unquote + +from moto.core.responses import BaseResponse + +from .models import BackupBackend, backup_backends + + +class BackupResponse(BaseResponse): + """Handler for Backup requests and responses.""" + + def __init__(self) -> None: + super().__init__(service_name="backup") + + @property + def backup_backend(self) -> BackupBackend: + """Return backend instance specific for this region.""" + return backup_backends[self.current_account][self.region] + + def create_backup_plan(self) -> str: + params = json.loads(self.body) + backup_plan = params.get("BackupPlan") + backup_plan_tags = params.get("BackupPlanTags") + creator_request_id = params.get("CreatorRequestId") + plan = self.backup_backend.create_backup_plan( + backup_plan=backup_plan, + backup_plan_tags=backup_plan_tags, + creator_request_id=creator_request_id, + ) + return json.dumps(dict(plan.to_dict())) + + def get_backup_plan(self) -> str: + params = self._get_params() + backup_plan_id = self.path.split("/")[-2] + version_id = params.get("versionId") + plan = self.backup_backend.get_backup_plan( + backup_plan_id=backup_plan_id, version_id=version_id + ) + return json.dumps(dict(plan.to_get_dict())) + + def delete_backup_plan(self) -> str: + backup_plan_id = self.path.split("/")[-1] + ( + backup_plan_id, + backup_plan_arn, + deletion_date, + version_id, + ) = self.backup_backend.delete_backup_plan( + backup_plan_id=backup_plan_id, + ) + return json.dumps( + dict( + BackupPlanId=backup_plan_id, + BackupPlanArn=backup_plan_arn, + DeletionDate=deletion_date, + VersionId=version_id, + ) + ) + + def list_backup_plans(self) -> str: + params = self._get_params() + include_deleted = params.get("includeDeleted") + backup_plans_list = self.backup_backend.list_backup_plans( + include_deleted=include_deleted + ) + return json.dumps( + dict(BackupPlansList=[p.to_list_dict() for p in backup_plans_list]) + ) + + def create_backup_vault(self) -> str: + params = json.loads(self.body) + backup_vault_name = self.path.split("/")[-1] + backup_vault_tags = params.get("BackupVaultTags") + encryption_key_arn = params.get("EncryptionKeyArn") + creator_request_id = params.get("CreatorRequestId") + backup_vault = self.backup_backend.create_backup_vault( + backup_vault_name=backup_vault_name, + backup_vault_tags=backup_vault_tags, + encryption_key_arn=encryption_key_arn, + creator_request_id=creator_request_id, + ) + return json.dumps(dict(backup_vault.to_dict())) + + def list_backup_vaults(self) -> str: + backup_vault_list = self.backup_backend.list_backup_vaults() + return json.dumps( + dict(BackupVaultList=[v.to_list_dict() for v in backup_vault_list]) + ) + + def list_tags(self) -> str: + resource_arn = unquote(self.path.split("/")[-2]) + tags = self.backup_backend.list_tags( + resource_arn=resource_arn, + ) + return json.dumps(dict(Tags=tags)) + + def tag_resource(self) -> str: + params = json.loads(self.body) + resource_arn = unquote(self.path.split("/")[-1]) + tags = params.get("Tags") + self.backup_backend.tag_resource( + resource_arn=resource_arn, + tags=tags, + ) + return "{}" + + def untag_resource(self) -> str: + params = json.loads(self.body) + resource_arn = unquote(self.path.split("/")[-1]) + tag_key_list = params.get("TagKeyList") + self.backup_backend.untag_resource( + resource_arn=resource_arn, + tag_key_list=tag_key_list, + ) + return "{}" diff --git a/moto/backup/urls.py b/moto/backup/urls.py new file mode 100644 index 000000000..5b4b7c84b --- /dev/null +++ b/moto/backup/urls.py @@ -0,0 +1,19 @@ +"""backup base URL and path.""" +from .responses import BackupResponse + +url_bases = [ + r"https?://backup\.(.+)\.amazonaws\.com", +] + + +response = BackupResponse() + + +url_paths = { + "{0}/backup/plans/?$": response.dispatch, + "{0}/backup/plans/(?P.+)/?$": response.dispatch, + "{0}/backup-vaults/$": response.dispatch, + "{0}/backup-vaults/(?P[^/]+)$": response.dispatch, + "{0}/tags/(?P.+)$": response.dispatch, + "{0}/untag/(?P.+)$": response.dispatch, +} diff --git a/moto/resourcegroupstaggingapi/models.py b/moto/resourcegroupstaggingapi/models.py index 41219e4d5..90c7a76ab 100644 --- a/moto/resourcegroupstaggingapi/models.py +++ b/moto/resourcegroupstaggingapi/models.py @@ -2,6 +2,7 @@ from typing import Any, Dict, Iterator, List, Optional, Tuple from moto.acm.models import AWSCertificateManagerBackend, acm_backends from moto.awslambda.models import LambdaBackend, lambda_backends +from moto.backup.models import BackupBackend, backup_backends from moto.core import BackendDict, BaseBackend from moto.core.exceptions import RESTError from moto.ec2 import ec2_backends @@ -100,6 +101,10 @@ class ResourceGroupsTaggingAPIBackend(BaseBackend): def sqs_backend(self) -> SQSBackend: return sqs_backends[self.account_id][self.region_name] + @property + def backup_backend(self) -> BackupBackend: + return backup_backends[self.account_id][self.region_name] + def _get_resources_generator( self, tag_filters: Optional[List[Dict[str, Any]]] = None, @@ -165,6 +170,19 @@ class ResourceGroupsTaggingAPIBackend(BaseBackend): continue yield {"ResourceARN": f"{certificate.arn}", "Tags": tags} + # Backup + if not resource_type_filters or "backup" in resource_type_filters: + for vault in self.backup_backend.vaults.values(): + tags = self.backup_backend.tagger.list_tags_for_resource( + vault.backup_vault_arn + )["Tags"] + if not tags or not tag_filter( + tags + ): # Skip if no tags, or invalid filter + continue + + yield {"ResourceARN": f"{vault.backup_vault_arn}", "Tags": tags} + # S3 if not resource_type_filters or "s3" in resource_type_filters: for bucket in self.s3_backend.buckets.values(): diff --git a/tests/test_backup/__init__.py b/tests/test_backup/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/test_backup/test_backup.py b/tests/test_backup/test_backup.py new file mode 100644 index 000000000..030bd9eb9 --- /dev/null +++ b/tests/test_backup/test_backup.py @@ -0,0 +1,359 @@ +"""Unit tests for backup-supported APIs.""" +import boto3 +import pytest +from botocore.exceptions import ClientError + +from moto import mock_backup + + +@mock_backup +def test_create_backup_plan(): + client = boto3.client("backup", region_name="eu-west-1") + response = client.create_backup_vault( + BackupVaultName="backupvault-foobar", + ) + resp = client.create_backup_plan( + BackupPlan={ + "BackupPlanName": "backupplan-foobar", + "Rules": [ + { + "RuleName": "foobar", + "TargetBackupVaultName": response["BackupVaultName"], + }, + ], + }, + ) + assert "BackupPlanId" in resp + assert "BackupPlanArn" in resp + assert "CreationDate" in resp + assert "VersionId" in resp + + +@mock_backup +def test_create_backup_plan_already_exists(): + client = boto3.client("backup", region_name="eu-west-1") + backup_plan_name = "backup_plan_foobar" + rules = [ + { + "RuleName": "foobar", + "TargetBackupVaultName": "backup-vault-foobar", + }, + ] + client.create_backup_plan( + BackupPlan={"BackupPlanName": backup_plan_name, "Rules": rules} + ) + + with pytest.raises(ClientError) as exc: + client.create_backup_plan( + BackupPlan={"BackupPlanName": backup_plan_name, "Rules": rules} + ) + err = exc.value.response["Error"] + assert err["Code"] == "AlreadyExistsException" + + +@mock_backup +def test_get_backup_plan(): + client = boto3.client("backup", region_name="eu-west-1") + response = client.create_backup_vault( + BackupVaultName="backupvault-foobar", + ) + plan = client.create_backup_plan( + BackupPlan={ + "BackupPlanName": "backupplan-foobar", + "Rules": [ + { + "RuleName": "foobar", + "TargetBackupVaultName": response["BackupVaultName"], + }, + ], + }, + ) + resp = client.get_backup_plan( + BackupPlanId=plan["BackupPlanId"], VersionId=plan["VersionId"] + ) + assert "BackupPlan" in resp + + +@mock_backup +def test_get_backup_plan_invalid_id(): + client = boto3.client("backup", region_name="eu-west-1") + + with pytest.raises(ClientError) as exc: + client.get_backup_plan(BackupPlanId="foobar") + err = exc.value.response["Error"] + assert err["Code"] == "ResourceNotFoundException" + + +@mock_backup +def test_get_backup_plan_invalid_version_id(): + client = boto3.client("backup", region_name="eu-west-1") + + plan = client.create_backup_plan( + BackupPlan={ + "BackupPlanName": "backupplan-foobar", + "Rules": [ + { + "RuleName": "foobar", + "TargetBackupVaultName": "Backup-vault-foobar", + }, + ], + }, + ) + with pytest.raises(ClientError) as exc: + client.get_backup_plan(BackupPlanId=plan["BackupPlanId"], VersionId="foobar") + err = exc.value.response["Error"] + assert err["Code"] == "ResourceNotFoundException" + + +@mock_backup +def test_get_backup_plan_with_multiple_rules(): + client = boto3.client("backup", region_name="eu-west-1") + plan = client.create_backup_plan( + BackupPlan={ + "BackupPlanName": "backupplan-foobar", + "Rules": [ + { + "RuleName": "rule1", + "TargetBackupVaultName": "backupvault-foobar", + "ScheduleExpression": "cron(0 1 ? * * *)", + "StartWindowMinutes": 60, + "CompletionWindowMinutes": 120, + }, + { + "RuleName": "rule2", + "TargetBackupVaultName": "backupvault-foobar", + }, + ], + }, + ) + resp = client.get_backup_plan(BackupPlanId=plan["BackupPlanId"]) + for rule in resp["BackupPlan"]["Rules"]: + assert "ScheduleExpression" in rule + assert "StartWindowMinutes" in rule + assert "CompletionWindowMinutes" in rule + assert "RuleId" in rule + + +@mock_backup +def test_delete_backup_plan(): + client = boto3.client("backup", region_name="eu-west-1") + response = client.create_backup_vault( + BackupVaultName="backupvault-foobar", + ) + plan = client.create_backup_plan( + BackupPlan={ + "BackupPlanName": "backupplan-foobar", + "Rules": [ + { + "RuleName": "foobar", + "TargetBackupVaultName": response["BackupVaultName"], + }, + ], + }, + ) + + resp = client.delete_backup_plan(BackupPlanId=plan["BackupPlanId"]) + assert "BackupPlanId" in resp + assert "BackupPlanArn" in resp + assert "DeletionDate" in resp + assert "VersionId" in resp + + resp = client.get_backup_plan( + BackupPlanId=plan["BackupPlanId"], VersionId=plan["VersionId"] + ) + assert "DeletionDate" in resp + + +@mock_backup +def test_delete_backup_plan_invalid_id(): + client = boto3.client("backup", region_name="eu-west-1") + + with pytest.raises(ClientError) as exc: + client.delete_backup_plan(BackupPlanId="foobar") + err = exc.value.response["Error"] + assert err["Code"] == "ResourceNotFoundException" + + +@mock_backup +def test_list_backup_plans(): + client = boto3.client("backup", region_name="eu-west-1") + for i in range(1, 3): + client.create_backup_plan( + BackupPlan={ + "BackupPlanName": f"backup-plan-{i}", + "Rules": [ + { + "RuleName": "foobar", + "TargetBackupVaultName": "backupvault-foobar", + }, + ], + }, + ) + resp = client.list_backup_plans() + backup_plans = resp["BackupPlansList"] + assert backup_plans[0]["BackupPlanName"] == "backup-plan-1" + assert backup_plans[1]["BackupPlanName"] == "backup-plan-2" + assert len(backup_plans) == 2 + assert resp["ResponseMetadata"]["HTTPStatusCode"] == 200 + + +@mock_backup +def test_list_backup_plans_without_include_deleted(): + client = boto3.client("backup", region_name="eu-west-1") + + for i in range(1, 3): + client.create_backup_plan( + BackupPlan={ + "BackupPlanName": f"backup-plan-{i}", + "Rules": [ + { + "RuleName": "foobar", + "TargetBackupVaultName": "backupvault-foobar", + }, + ], + }, + ) + resp = client.list_backup_plans() + client.delete_backup_plan(BackupPlanId=resp["BackupPlansList"][0]["BackupPlanId"]) + resp_list = client.list_backup_plans() + backup_plans = resp_list["BackupPlansList"] + assert backup_plans[0]["BackupPlanName"] == "backup-plan-2" + assert len(backup_plans) == 1 + + +@mock_backup +def test_list_backup_plans_with_include_deleted(): + client = boto3.client("backup", region_name="eu-west-1") + for i in range(1, 3): + client.create_backup_plan( + BackupPlan={ + "BackupPlanName": f"backup-plan-{i}", + "Rules": [ + { + "RuleName": "foobar", + "TargetBackupVaultName": "backupvault-foobar", + }, + ], + }, + ) + resp = client.list_backup_plans() + client.delete_backup_plan(BackupPlanId=resp["BackupPlansList"][0]["BackupPlanId"]) + resp_list = client.list_backup_plans(IncludeDeleted=True) + backup_plans = resp_list["BackupPlansList"] + assert backup_plans[0]["BackupPlanName"] == "backup-plan-1" + assert backup_plans[1]["BackupPlanName"] == "backup-plan-2" + assert len(backup_plans) == 2 + + +@mock_backup +def test_create_backup_vault(): + client = boto3.client("backup", region_name="eu-west-1") + resp = client.create_backup_vault( + BackupVaultName="backupvault-foobar", + BackupVaultTags={ + "foo": "bar", + }, + ) + assert "BackupVaultName" in resp + assert "BackupVaultArn" in resp + assert "CreationDate" in resp + + +@mock_backup +def test_create_backup_vault_already_exists(): + client = boto3.client("backup", region_name="eu-west-1") + backup_vault_name = "backup_vault_foobar" + client.create_backup_vault(BackupVaultName=backup_vault_name) + + with pytest.raises(ClientError) as exc: + client.create_backup_vault(BackupVaultName=backup_vault_name) + err = exc.value.response["Error"] + assert err["Code"] == "AlreadyExistsException" + + +@mock_backup +def test_list_backup_vaults(): + client = boto3.client("backup", region_name="eu-west-1") + for i in range(1, 3): + client.create_backup_vault( + BackupVaultName=f"backup-vault-{i}", + ) + resp = client.list_backup_vaults() + backup_plans = resp["BackupVaultList"] + assert backup_plans[0]["BackupVaultName"] == "backup-vault-1" + assert backup_plans[1]["BackupVaultName"] == "backup-vault-2" + assert resp["ResponseMetadata"]["HTTPStatusCode"] == 200 + + +@mock_backup +def test_list_tags_vault(): + client = boto3.client("backup", region_name="eu-west-1") + vault = client.create_backup_vault( + BackupVaultName="backupvault-foobar", + BackupVaultTags={ + "key1": "value1", + "key2": "value2", + }, + ) + resp = client.list_tags(ResourceArn=vault["BackupVaultArn"]) + assert resp["Tags"] == {"key1": "value1", "key2": "value2"} + + +@mock_backup +def test_list_tags_plan(): + client = boto3.client("backup", region_name="eu-west-1") + response = client.create_backup_vault( + BackupVaultName="backupvault-foobar", + ) + plan = client.create_backup_plan( + BackupPlan={ + "BackupPlanName": "backupplan-foobar", + "Rules": [ + { + "RuleName": "foobar", + "TargetBackupVaultName": response["BackupVaultName"], + }, + ], + }, + BackupPlanTags={ + "key1": "value1", + "key2": "value2", + }, + ) + resp = client.list_tags(ResourceArn=plan["BackupPlanArn"]) + assert resp["Tags"] == {"key1": "value1", "key2": "value2"} + + +@mock_backup +def test_tag_resource(): + client = boto3.client("backup", region_name="eu-west-1") + vault = client.create_backup_vault( + BackupVaultName="backupvault-foobar", + BackupVaultTags={ + "key1": "value1", + }, + ) + resource_arn = vault["BackupVaultArn"] + client.tag_resource( + ResourceArn=resource_arn, Tags={"key2": "value2", "key3": "value3"} + ) + resp = client.list_tags(ResourceArn=resource_arn) + assert resp["Tags"] == {"key1": "value1", "key2": "value2", "key3": "value3"} + + +@mock_backup +def test_untag_resource(): + client = boto3.client("backup", region_name="eu-west-1") + vault = client.create_backup_vault( + BackupVaultName="backupvault-foobar", + BackupVaultTags={ + "key1": "value1", + }, + ) + resource_arn = vault["BackupVaultArn"] + client.tag_resource( + ResourceArn=resource_arn, Tags={"key2": "value2", "key3": "value3"} + ) + resp = client.untag_resource(ResourceArn=resource_arn, TagKeyList=["key2"]) + resp = client.list_tags(ResourceArn=resource_arn) + assert resp["Tags"] == {"key1": "value1", "key3": "value3"} diff --git a/tests/test_resourcegroupstaggingapi/test_resourcegroupstaggingapi.py b/tests/test_resourcegroupstaggingapi/test_resourcegroupstaggingapi.py index 88fc908c1..079466116 100644 --- a/tests/test_resourcegroupstaggingapi/test_resourcegroupstaggingapi.py +++ b/tests/test_resourcegroupstaggingapi/test_resourcegroupstaggingapi.py @@ -5,6 +5,7 @@ from botocore.client import ClientError from moto import ( mock_acm, + mock_backup, mock_cloudformation, mock_ec2, mock_ecs, @@ -117,6 +118,37 @@ def test_get_resources_acm(): ) +@mock_backup +@mock_resourcegroupstaggingapi +def test_get_resources_backup(): + backup = boto3.client("backup", region_name="eu-central-1") + + # Create two tagged Backup Vaults + for i in range(1, 3): + i_str = str(i) + + backup.create_backup_vault( + BackupVaultName="backup-vault-tag-" + i_str, + BackupVaultTags={ + "Test": i_str, + }, + ) + + rtapi = boto3.client("resourcegroupstaggingapi", region_name="eu-central-1") + + # Basic test + resp = rtapi.get_resources(ResourceTypeFilters=["backup"]) + assert len(resp["ResourceTagMappingList"]) == 2 + + # Test tag filtering + resp = rtapi.get_resources( + ResourceTypeFilters=["backup"], + TagFilters=[{"Key": "Test", "Values": ["1"]}], + ) + assert len(resp["ResourceTagMappingList"]) == 1 + assert {"Key": "Test", "Value": "1"} in resp["ResourceTagMappingList"][0]["Tags"] + + @mock_ecs @mock_ec2 @mock_resourcegroupstaggingapi