Move project restore from EE to CE in projects API
What does this MR do and why?
Due to a recent data-loss incident, the decision was made to down-tier delayed deletion. This MR moves delayed deletion logic from the EE code for the projects restore API into CE. The down-tier is gated by the downtier_delayed_deletion feature flag and the API functions as normal unless that feature flag is enabled.
References
Related to #534189 (closed)
How to set up and validate locally
- Run GDK in SaaS mode.
export GITLAB_SIMULATE_SAAS=1 - Enable the feature flag
Feature.enable(:downtier_delayed_deletion) - Delete and restore a project in a group with the API. Both actions should be successful.
▶ curl --header 'PRIVATE-TOKEN: TOKEN' -X DELETE 'https://gdk.test:3443/api/v4/projects/72' {"message":"202 Accepted"} ▶ curl --header 'PRIVATE-TOKEN: TOKEN' -X POST 'https://gdk.test:3443/api/v4/projects/72/restore' {"id":72,"description":null,"name":"disabled","name_with_namespace":"test-delete / disabled","path":"disabled","path_with_namespace":"test-delete/disabled","created_at":"2025-04-07T19:54:18.184Z","default_branch":"main","tag_list":[],"topics":[],"ssh_url_to_repo":"ssh://git@gdk.test:2222/test-delete/disabled.git","http_url_to_repo":"https://gdk.test:3443/test-delete/disabled.git","web_url":"https://gdk.test:3443/test-delete/disabled","readme_url":"https://gdk.test:3443/test-delete/disabled/-/blob/main/README.md","forks_count":0,"avatar_url":null,"star_count":0,"last_activity_at":"2025-04-07T19:55:23.002Z","namespace":{"id":143,"name":"test-delete","path":"test-delete","kind":"group","full_path":"test-delete","parent_id":null,"avatar_url":null,"web_url":"https://gdk.test:3443/groups/test-delete"},"repository_storage":"default","_links":{"self":"https://gdk.test:3443/api/v4/projects/72","issues":"https://gdk.test:3443/api/v4/projects/72/issues","merge_requests":"https://gdk.test:3443/api/v4/projects/72/merge_requests","repo_branches":"https://gdk.test:3443/api/v4/projects/72/repository/branches","labels":"https://gdk.test:3443/api/v4/projects/72/labels","events":"https://gdk.test:3443/api/v4/projects/72/events","members":"https://gdk.test:3443/api/v4/projects/72/members","cluster_agents":"https://gdk.test:3443/api/v4/projects/72/cluster_agents"},"packages_enabled":true,"empty_repo":false,"archived":false,"visibility":"private","resolve_outdated_diff_discussions":false,"container_expiration_policy":{"cadence":"1d","enabled":false,"keep_n":10,"older_than":"90d","name_regex":".*","name_regex_keep":null,"next_run_at":"2025-04-08T19:54:18.210Z"},"repository_object_format":"sha1","issues_enabled":true,"merge_requests_enabled":true,"wiki_enabled":true,"jobs_enabled":true,"snippets_enabled":true,"container_registry_enabled":true,"service_desk_enabled":false,"service_desk_address":null,"can_create_merge_request_in":true,"issues_access_level":"enabled","repository_access_level":"enabled","merge_requests_access_level":"enabled","forking_access_level":"enabled","wiki_access_level":"enabled","builds_access_level":"enabled","snippets_access_level":"enabled","pages_access_level":"private","analytics_access_level":"enabled","container_registry_access_level":"enabled","security_and_compliance_access_level":"private","releases_access_level":"enabled","environments_access_level":"enabled","feature_flags_access_level":"enabled","infrastructure_access_level":"enabled","monitor_access_level":"enabled","model_experiments_access_level":"enabled","model_registry_access_level":"enabled","emails_disabled":false,"emails_enabled":true,"shared_runners_enabled":true,"lfs_enabled":true,"creator_id":1,"import_url":null,"import_type":null,"import_status":"none","open_issues_count":0,"description_html":"","updated_at":"2025-04-07T19:55:23.057Z","ci_default_git_depth":20,"ci_delete_pipelines_in_seconds":null,"ci_forward_deployment_enabled":true,"ci_forward_deployment_rollback_allowed":true,"ci_job_token_scope_enabled":false,"ci_separated_caches":true,"ci_allow_fork_pipelines_to_run_in_parent_project":true,"ci_id_token_sub_claim_components":["project_path","ref_type","ref"],"build_git_strategy":"fetch","keep_latest_artifact":true,"restrict_user_defined_variables":false,"ci_pipeline_variables_minimum_override_role":"developer","runners_token":"GR1348941SZ7Fs2QJWvjdhbagN-y5","runner_token_expiration_interval":null,"group_runners_enabled":true,"auto_cancel_pending_pipelines":"enabled","build_timeout":3600,"auto_devops_enabled":true,"auto_devops_deploy_strategy":"continuous","ci_push_repository_for_job_token_allowed":false,"ci_config_path":null,"public_jobs":true,"shared_with_groups":[],"only_allow_merge_if_pipeline_succeeds":false,"allow_merge_on_skipped_pipeline":null,"request_access_enabled":true,"only_allow_merge_if_all_discussions_are_resolved":false,"remove_source_branch_after_merge":true,"printing_merge_request_link_enabled":true,"merge_method":"merge","squash_option":"default_off","enforce_auth_checks_on_uploads":true,"suggestion_commit_message":null,"merge_commit_template":null,"squash_commit_template":null,"issue_branch_template":null,"warn_about_potentially_unwanted_characters":true,"autoclose_referenced_issues":true,"max_artifacts_size":null,"external_authorization_classification_label":null,"requirements_enabled":false,"requirements_access_level":"enabled","security_and_compliance_enabled":true,"compliance_frameworks":[]} - Disable the feature flag
Feature.disable(:downtier_delayed_deletion) - Delete and restore a project in a group with the API. The restore should fail with a 404 error.
▶ curl --header 'PRIVATE-TOKEN: TOKEN' -X DELETE 'https://gdk.test:3443/api/v4/projects/72' {"message":"202 Accepted"} ▶ curl --header 'PRIVATE-TOKEN: TOKEN' -X POST 'https://gdk.test:3443/api/v4/projects/72/restore' {"message":"404 Group Not Found"}
MR acceptance checklist
Evaluate this MR against the MR acceptance checklist. It helps you analyze changes to reduce risks in quality, performance, reliability, security, and maintainability.
Edited by Ian Anderson