From 6c7dff6af695ae3c9501aed2e599fc089658e86c Mon Sep 17 00:00:00 2001
From: archinksagar <68829863+archinksagar@users.noreply.github.com>
Date: Fri, 26 Jan 2024 16:49:48 -0500
Subject: [PATCH] Feature: Backup api (#7231)
---
IMPLEMENTATION_COVERAGE.md | 181 ++++-----
docs/docs/services/appsync.rst | 2 -
docs/docs/services/backup.rst | 119 ++++++
docs/docs/services/codecommit.rst | 1 -
docs/docs/services/efs.rst | 1 -
docs/docs/services/eks.rst | 11 -
docs/docs/services/elasticache.rst | 9 -
docs/docs/services/emr.rst | 1 -
docs/docs/services/guardduty.rst | 1 -
docs/docs/services/iot.rst | 5 -
docs/docs/services/logs.rst | 1 -
docs/docs/services/mediaconnect.rst | 1 -
docs/docs/services/opensearch.rst | 5 -
docs/docs/services/quicksight.rst | 1 -
docs/docs/services/rds.rst | 6 +-
docs/docs/services/s3.rst | 2 -
docs/docs/services/sagemaker.rst | 14 -
docs/docs/services/transcribe.rst | 4 -
moto/__init__.py | 1 +
moto/backend_index.py | 3 +-
moto/backup/__init__.py | 5 +
moto/backup/exceptions.py | 16 +
moto/backup/models.py | 236 ++++++++++++
moto/backup/responses.py | 116 ++++++
moto/backup/urls.py | 19 +
moto/resourcegroupstaggingapi/models.py | 18 +
tests/test_backup/__init__.py | 0
tests/test_backup/test_backup.py | 359 ++++++++++++++++++
.../test_resourcegroupstaggingapi.py | 32 ++
29 files changed, 1019 insertions(+), 151 deletions(-)
create mode 100644 docs/docs/services/backup.rst
create mode 100644 moto/backup/__init__.py
create mode 100644 moto/backup/exceptions.py
create mode 100644 moto/backup/models.py
create mode 100644 moto/backup/responses.py
create mode 100644 moto/backup/urls.py
create mode 100644 tests/test_backup/__init__.py
create mode 100644 tests/test_backup/test_backup.py
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