Resolve "Add Delete permanently action for marked-for-deletion Terraform states"

What does this MR do and why?

Adds a TerraformStateDeletePermanently GraphQL mutation. It lets a user permanently delete a Terraform state that is already marked for deletion, without waiting for the grace period.

This is part of the Terraform state delayed deletion epic: gitlab-org#23396

Resolves #621470 (closed)

The flow has two steps, mirroring project deletion: Remove state, then Delete permanently. The mutation rejects a state that has not been marked for deletion, so there is no single-step permanent delete.

Changes

  • Mutation: Mutations::Terraform::State::DeletePermanently finds the state with authorized_find!, calls the new service, and returns { errors: response.errors }. It is registered in MutationType as an experiment for milestone 19.6 and uses the delete_terraform_state granular token permission.
  • Service: Terraform::States::PermanentDestroyService returns an error when the user lacks admin_terraform_state or when deleted_at is nil. Otherwise it enqueues Terraform::States::DestroyWorker with permanent set to true.
  • Worker and destroy service: DestroyWorker#perform accepts a permanent argument (default false) and passes it to DestroyService. With permanent: true, DestroyService skips the grace period check. Regular deletion is unchanged.
  • Audit event (EE): EE::Terraform::States::PermanentDestroyService records a new terraform_state_permanently_deleted event, separate from the regular deletion event.
  • Docs: updates the GraphQL reference, the fine-grained token docs and the audit event types page.
  • Authorization: use granular delete_terraform_state
    • Terraform::States::PermanentDestroyService checked admin_terraform_state, which Gitlab/Authz/PermissionCheck flags as a coarse admin_* permission. It now checks the granular delete_terraform_state permission, which already has a definition in config/authz/permissions/terraform_state/delete.yml and is used by the GraphQL mutations for the granular token check.
    • app/services/terraform/states/permanent_destroy_service.rb: can?(:delete_terraform_state, state.project) replaces can?(:admin_terraform_state, state.project).
    • app/policies/project_policy.rb: prevent :delete_terraform_state in the infrastructure_disabled | terraform_state_disabled block, so the permission is denied when Terraform state is disabled.
    • spec/policies/project_policy_spec.rb: added :delete_terraform_state to the maintainer permission lists, for both the enabled and the disabled Terraform state contexts.
Notes
  • Behavior is unchanged: only Maintainers and above can permanently delete a state.
  • TriggerDestroyService and Mutations::Terraform::State::Base still use admin_terraform_state. They are already in .rubocop_todo/gitlab/authz/permission_check.yml, so they are out of scope here.

Notes for reviewers

  • Permission differs from the issue. The issue says to reuse admin_terraform_state. This MR checks delete_terraform_state instead, because Gitlab/Authz/PermissionCheck flags new admin_* checks as coarse. No access change: ProjectPolicy enables delete_terraform_state for anyone with admin_terraform_state (Maintainers and above), and prevents it when Terraform state or infrastructure is disabled. The permission was already defined in config/authz/permissions/terraform_state/delete.yml and is used for the granular token check on this mutation. No separate permission for permanent deletion is added. The two-step flow (Remove state, then Delete permanently) is the guard.
  • DestroyWorker takes permanent as a positional argument, so the service calls perform_async(state.id, true). The issue shows permanent: true, but Sidekiq job arguments don't support keyword arguments.
  • PermanentDestroyService does not check the terraform_state_delayed_deletion feature flag. Permanent deletion only skips a grace period check, which only applies when that flag is on.

References

How to set up and validate locally

Run the specs:

bundle exec rspec \
  spec/workers/terraform/states/destroy_worker_spec.rb \
  spec/services/terraform/states/destroy_service_spec.rb \
  spec/services/terraform/states/permanent_destroy_service_spec.rb \
  spec/requests/api/graphql/mutations/terraform/state/delete_permanently_spec.rb

To try it in GraphiQL, enable terraform_state_delayed_deletion for a project and mark a state for deletion. Then run:

mutation {
  terraformStateDeletePermanently(input: { id: "gid://gitlab/Terraform::State/<id>" }) {
    errors
  }
}
  • Maintainer: returns no errors, and the state is destroyed.
  • Developer: returns a permission error.
  • State not marked for deletion: returns an error.

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.

Related to #621470 (closed)

Edited by Tony TeKare

Merge request reports

Loading
Loading